summaryrefslogtreecommitdiffstats
path: root/dom/xul/templates
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/xul/templates
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/xul/templates')
-rw-r--r--dom/xul/templates/crashtests/257752-1-recursion.rdf13
-rw-r--r--dom/xul/templates/crashtests/257752-1-recursion.xul28
-rw-r--r--dom/xul/templates/crashtests/329884-1.xul20
-rw-r--r--dom/xul/templates/crashtests/330012-1.rdf13
-rw-r--r--dom/xul/templates/crashtests/330012-1.xul22
-rw-r--r--dom/xul/templates/crashtests/404346-1.xul7
-rw-r--r--dom/xul/templates/crashtests/415019-1.xul14
-rw-r--r--dom/xul/templates/crashtests/417840-1.xul1
-rw-r--r--dom/xul/templates/crashtests/424418-1.xul1
-rw-r--r--dom/xul/templates/crashtests/crashtests.list7
-rw-r--r--dom/xul/templates/moz.build58
-rw-r--r--dom/xul/templates/nsContentSupportMap.cpp18
-rw-r--r--dom/xul/templates/nsContentSupportMap.h62
-rw-r--r--dom/xul/templates/nsContentTestNode.cpp90
-rw-r--r--dom/xul/templates/nsContentTestNode.h48
-rw-r--r--dom/xul/templates/nsIXULBuilderListener.idl28
-rw-r--r--dom/xul/templates/nsIXULSortService.idl42
-rw-r--r--dom/xul/templates/nsIXULTemplateBuilder.idl409
-rw-r--r--dom/xul/templates/nsIXULTemplateQueryProcessor.idl276
-rw-r--r--dom/xul/templates/nsIXULTemplateResult.idl116
-rw-r--r--dom/xul/templates/nsIXULTemplateRuleFilter.idl36
-rw-r--r--dom/xul/templates/nsInstantiationNode.cpp83
-rw-r--r--dom/xul/templates/nsInstantiationNode.h37
-rw-r--r--dom/xul/templates/nsRDFBinding.cpp265
-rw-r--r--dom/xul/templates/nsRDFBinding.h216
-rw-r--r--dom/xul/templates/nsRDFConInstanceTestNode.cpp281
-rw-r--r--dom/xul/templates/nsRDFConInstanceTestNode.h88
-rw-r--r--dom/xul/templates/nsRDFConMemberTestNode.cpp510
-rw-r--r--dom/xul/templates/nsRDFConMemberTestNode.h77
-rw-r--r--dom/xul/templates/nsRDFPropertyTestNode.cpp362
-rw-r--r--dom/xul/templates/nsRDFPropertyTestNode.h104
-rw-r--r--dom/xul/templates/nsRDFQuery.cpp47
-rw-r--r--dom/xul/templates/nsRDFQuery.h130
-rw-r--r--dom/xul/templates/nsRDFTestNode.h49
-rw-r--r--dom/xul/templates/nsResourceSet.cpp105
-rw-r--r--dom/xul/templates/nsResourceSet.h82
-rw-r--r--dom/xul/templates/nsRuleNetwork.cpp428
-rw-r--r--dom/xul/templates/nsRuleNetwork.h861
-rw-r--r--dom/xul/templates/nsTemplateMap.h64
-rw-r--r--dom/xul/templates/nsTemplateMatch.cpp35
-rw-r--r--dom/xul/templates/nsTemplateMatch.h139
-rw-r--r--dom/xul/templates/nsTemplateRule.cpp422
-rw-r--r--dom/xul/templates/nsTemplateRule.h328
-rw-r--r--dom/xul/templates/nsTreeRows.cpp482
-rw-r--r--dom/xul/templates/nsTreeRows.h437
-rw-r--r--dom/xul/templates/nsXMLBinding.cpp118
-rw-r--r--dom/xul/templates/nsXMLBinding.h137
-rw-r--r--dom/xul/templates/nsXULContentBuilder.cpp1976
-rw-r--r--dom/xul/templates/nsXULContentUtils.cpp366
-rw-r--r--dom/xul/templates/nsXULContentUtils.h149
-rw-r--r--dom/xul/templates/nsXULResourceList.h13
-rw-r--r--dom/xul/templates/nsXULSortService.cpp507
-rw-r--r--dom/xul/templates/nsXULSortService.h187
-rw-r--r--dom/xul/templates/nsXULTemplateBuilder.cpp2573
-rw-r--r--dom/xul/templates/nsXULTemplateBuilder.h502
-rw-r--r--dom/xul/templates/nsXULTemplateQueryProcessorRDF.cpp1825
-rw-r--r--dom/xul/templates/nsXULTemplateQueryProcessorRDF.h349
-rw-r--r--dom/xul/templates/nsXULTemplateQueryProcessorStorage.cpp502
-rw-r--r--dom/xul/templates/nsXULTemplateQueryProcessorStorage.h69
-rw-r--r--dom/xul/templates/nsXULTemplateQueryProcessorXML.cpp449
-rw-r--r--dom/xul/templates/nsXULTemplateQueryProcessorXML.h170
-rw-r--r--dom/xul/templates/nsXULTemplateResultRDF.cpp208
-rw-r--r--dom/xul/templates/nsXULTemplateResultRDF.h81
-rw-r--r--dom/xul/templates/nsXULTemplateResultSetRDF.cpp82
-rw-r--r--dom/xul/templates/nsXULTemplateResultSetRDF.h60
-rw-r--r--dom/xul/templates/nsXULTemplateResultStorage.cpp126
-rw-r--r--dom/xul/templates/nsXULTemplateResultStorage.h37
-rw-r--r--dom/xul/templates/nsXULTemplateResultXML.cpp189
-rw-r--r--dom/xul/templates/nsXULTemplateResultXML.h59
-rw-r--r--dom/xul/templates/nsXULTreeBuilder.cpp1881
-rw-r--r--dom/xul/templates/tests/chrome/animals.rdf224
-rw-r--r--dom/xul/templates/tests/chrome/animals.sqlitebin0 -> 5120 bytes
-rw-r--r--dom/xul/templates/tests/chrome/animals.xml19
-rw-r--r--dom/xul/templates/tests/chrome/bug441785-1.rdf263
-rw-r--r--dom/xul/templates/tests/chrome/bug441785-2.rdf11
-rw-r--r--dom/xul/templates/tests/chrome/chrome.ini225
-rw-r--r--dom/xul/templates/tests/chrome/file_bug330010.rdf13
-rw-r--r--dom/xul/templates/tests/chrome/templates_shared.js488
-rw-r--r--dom/xul/templates/tests/chrome/test_bug329335.xul28
-rw-r--r--dom/xul/templates/tests/chrome/test_bug330010.xul51
-rw-r--r--dom/xul/templates/tests/chrome/test_bug397148.xul19
-rw-r--r--dom/xul/templates/tests/chrome/test_bug441785.xul148
-rw-r--r--dom/xul/templates/tests/chrome/test_bug476634.xul76
-rw-r--r--dom/xul/templates/tests/chrome/test_sortservice.xul70
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_bindingsextendedsyntax.xul73
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_bindingsmultiple.xul80
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_bindingsquerysyntax.xul73
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_bindingsreversed.xul58
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_bindingssameastriple.xul58
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_containerandmembervariablechanged.xul88
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_containervariablechanged.xul49
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_containmentattribute.xul71
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_defaultcontainervariableisuri.xul49
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_errors.xul280
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_extendedsyntax.xul95
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxemptyconditions.xul48
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxotherrefvariable.xul95
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxremoveunmatched.xul55
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxsimplevariablesubstitution.xul54
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxtworulesrecurse.xul81
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxusinganinterveningcontainer.xul60
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_extendedvariablesubstitution.xul54
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_gridelement.xul131
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_htmlelementextendedsyntaxwithbinding.xul114
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_htmlelementquerysyntaxrecursive.xul81
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_htmlelementquerysyntaxwithmultiplerules.xul102
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_htmlelementsimplesyntax.xul81
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_htmlelementsimplesyntaxusingatextnode.xul81
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_invalidqp.xul48
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_listboxelement.xul117
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_literalasmember.xul52
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_membervariablechanged.xul49
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_membervariablesubstitution.xul54
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_menuelement.xul90
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_menuelementrecursive.xul121
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_menulistelement.xul90
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_mixedsyntaxiscontainer.xul67
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_mixedsyntaxiscontainerisempty.xul60
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_mixedsyntaxisempty.xul60
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_noaction.xul48
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_noactionuriattribute.xul55
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_parentconditions.xul113
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_parentcontenttag.xul114
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_parentsimplesyntax.xul110
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_query3triples.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_query3tripleswherecontains.xul111
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_querymember3tripleswhereequals.xul60
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_querymemberandtwotriples.xul58
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_querymembertriplemembertriple.xul64
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_queryresourcematch.xul58
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_queryreversetriple.xul53
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_queryselfwithtriple.xul51
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_querysetone.xul95
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_querysettwo.xul117
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_querysettwowithcondition.xul149
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_querysyntax.xul63
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_querysyntaxmultiplerules.xul115
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_querysyntaxmultiplerulesfirstconditionall.xul71
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_querysyntaxmultiplerulestwoconditions.xul113
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_querytripleandmembermerge.xul55
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_querytripleobjecttosubject.xul67
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_querytwomembers.xul107
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_querytwomembersfiltered.xul54
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_querytwotriples.xul55
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_queryupwardsmember.xul54
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_queryupwardsmembertripleandfilteringtriple.xul55
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_querywithemptyconditions.xul117
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_referenceasmember.xul65
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_regenerate.xul49
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_selfgenerationextendedsyntax.xul66
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_selfgenerationsimplesyntax.xul53
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxenclosedinacontainer.xul90
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxenclosedinacontainerwitharule.xul58
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxfilter.xul45
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxfilterwithmultiplerules.xul97
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxfilterwithrule.xul98
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxiteratingoverasinglevalue.xul86
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusinganinterveningcontainer.xul90
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingatextnode.xul50
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingcontainerasthegenerationelement.xul100
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingdontrecurse.xul52
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingrecursivegeneration.xul109
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingrecursivegenerationagain.xul64
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxwithtwovariablesused.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutioncaretsatbeginningandend.xul45
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutioncaretsubstitution.xul45
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionnovariable.xul45
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionquestionmarkaspartofvariable.xul45
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionquestionmarksubstitution.xul56
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutiontextandvariable.xul56
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionvariableandtextconcatenated.xul45
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionvariablesconcatenated.xul45
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sortascendinginteger.xul70
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sortascendingquerysyntax.xul114
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sortascendingtworulesquerysyntax.xul70
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sortascendingtworuleswithcontainerquerysyntax.xul80
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sortascendingtworuleswithdifferentcontainerquerysyntax.xul84
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sortdescendingquerysyntax.xul114
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sortquerymemberandtwotriples.xul58
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sortresource2descendingsimplesyntax.xul52
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sortresource2settopredicateascendingquerysyntax.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sortresource2settopredicatedescendingquerysyntax.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sortresourceascendingquerysyntax.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sortresourcedescendingquerysyntax.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sortresourcesettopredicateascendingquerysyntax.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sortresourcesettopredicatedescendingquerysyntax.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sorttworesourcesasstringsettopredicatedescendingquerysyntax.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sorttworesourcessettopredicateascendingquerysyntax.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sorttwovariablesascendingquerysyntax.xul119
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sorttwovariablesascendingsimplesyntax.xul52
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sorttwovariablesdescendingquerysyntax.xul64
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_sortunknownascendingquerysyntax.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_storage_bad_parameters.xul58
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_storage_bad_parameters_2.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_storage_bad_parameters_3.xul60
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_storage_baddatasource.xul56
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_storage_badquery.xul58
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_storage_dynamicparameters.xul84
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_storage_listbox.xul55
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_storage_multiqueries.xul86
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_storage_parameters.xul160
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_storage_rule.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_storage_simple.xul52
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_storage_sortintegerasc.xul75
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_storage_sortintegerdesc.xul78
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_storage_sortstringasc.xul75
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_storage_sortstringdesc.xul75
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_storage_tree.xul122
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntax.xul158
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxnotrecursive.xul145
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxnotrecursivetreebuilder.xul145
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursive.xul215
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursivemultiplerules.xul268
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursivemultiplerulestreebuilder.xul230
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursivetreebuilder.xul215
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxtreebuilder.xul158
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_treeelementsimplesyntaxnotrecursive.xul136
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_treeelementsimplesyntaxnotrecursivetreebuilder.xul136
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_treeelementsimplesyntaxrecursive.xul206
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_treeelementsimplesyntaxrecursivetreebuilder.xul206
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_treeelementtreecell.xul133
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_treeelementtreecellsortascending.xul133
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_treeelementtreecellsortascendingtreebuilder.xul133
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_treeelementtreecelltreebuilder.xul133
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_treeelementtreeitemonly.xul96
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_treeelementtreeitemsortascending.xul96
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_twogenerationnodes.xul100
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereafterignorecase.xul60
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereafterlowercase.xul63
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereafternegation.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereafteruppercase.xul60
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherebeforeignorecase.xul60
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherebeforelowercase.xul64
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherebeforenegation.xul58
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherebeforeuppercase.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherecontains.xul113
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherecontainsignorecase.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherecontainsnegation.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherecontainsnumber.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherecontainsnumberstring.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherecontainsresource.xul57
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherecontainstwo.xul113
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereendswith.xul57
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereendswithignorecase.xul58
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereendswithnegation.xul63
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereequals.xul109
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereequalsignorecase.xul57
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereequalsmultiple.xul58
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereequalsmultiplenegation.xul125
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereequalsmultiplenegationignorecase.xul60
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereequalsnegation.xul63
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereequalsnegationignorecase.xul63
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereequalsnegationwrongcase.xul64
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereequalsnumber.xul58
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereequalsothervariable.xul65
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereequalsresource.xul57
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereequalssamevariable.xul64
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereequalswrongcase.xul55
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wheregreater.xul60
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wheregreaternegation.xul60
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wheregreaternegationstring.xul60
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wheregreaterstring.xul113
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_whereless.xul62
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherelessnegation.xul61
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherelessnegationstring.xul61
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherelessstring.xul62
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherenorel.xul69
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherenosubject.xul69
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherenovalue.xul69
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherestartswith.xul108
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherestartswithignorecase.xul58
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherestartswithmultiple.xul59
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherestartswithnegation.xul62
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherestartswithunknownvariable.xul55
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wherestartswithvariable.xul64
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wheresubjectequalsvariable.xul57
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_wheresubjectstartswithvariable.xul57
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_xmlquerysimple.xul48
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithassign.xul68
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithassignmentandcondition.xul69
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithassignmentandconditiondontrecurse.xul60
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithbindinginbindings.xul54
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithbindinginrule.xul64
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithdifferentmember.xul48
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithinlinedata.xul54
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithinlinedatawithmultiplequeries.xul71
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithmultiplequeries.xul70
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithothertypes.xul75
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithsort.xul51
-rw-r--r--dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithsortotherfield.xul51
290 files changed, 37653 insertions, 0 deletions
diff --git a/dom/xul/templates/crashtests/257752-1-recursion.rdf b/dom/xul/templates/crashtests/257752-1-recursion.rdf
new file mode 100644
index 000000000..a6eeb104b
--- /dev/null
+++ b/dom/xul/templates/crashtests/257752-1-recursion.rdf
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:m="urn:foo#">
+ <rdf:Seq about="urn:x-rec:1">
+ <rdf:li rdf:resource="urn:x-rec:2"/>
+ </rdf:Seq>
+ <rdf:Seq about="urn:x-rec:2">
+ <rdf:li rdf:resource="urn:x-rec:3"/>
+ </rdf:Seq>
+ <rdf:Seq about="urn:x-rec:3">
+ <rdf:li rdf:resource="urn:x-rec:1"/>
+ </rdf:Seq>
+</rdf:RDF>
diff --git a/dom/xul/templates/crashtests/257752-1-recursion.xul b/dom/xul/templates/crashtests/257752-1-recursion.xul
new file mode 100644
index 000000000..fad5abfb6
--- /dev/null
+++ b/dom/xul/templates/crashtests/257752-1-recursion.xul
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window id="child-iterate-recurse"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<vbox flex="1" style="overflow: auto;">
+
+<vbox datasources="257752-1-recursion.rdf" ref="urn:x-rec:1">
+ <template>
+ <rule>
+ <conditions>
+ <content uri="?uri"/>
+ <member container="?uri" child="?child"/>
+ </conditions>
+ <action>
+ <vbox uri="?child" style="border: 1px solid grey; margin: 1em;">
+ <label value="hi"/>
+ </vbox>
+ </action>
+ </rule>
+ </template>
+</vbox>
+
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/crashtests/329884-1.xul b/dom/xul/templates/crashtests/329884-1.xul
new file mode 100644
index 000000000..8cc486e27
--- /dev/null
+++ b/dom/xul/templates/crashtests/329884-1.xul
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script>
+
+function rM(q1) { q1.parentNode.removeChild(q1); }
+
+function init2()
+{
+ rM(document.getElementById("t"));
+}
+
+window.addEventListener("load", init2, false);
+
+</script>
+
+<foo id="t" datasources="1.rdf" />
+
+</window>
diff --git a/dom/xul/templates/crashtests/330012-1.rdf b/dom/xul/templates/crashtests/330012-1.rdf
new file mode 100644
index 000000000..4bda7316a
--- /dev/null
+++ b/dom/xul/templates/crashtests/330012-1.rdf
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:s="urn:squarefree:a1:">
+ <rdf:Description about="urn:root">
+ <s:grapes>
+ <rdf:Bag>
+ <rdf:li>
+ <rdf:Description/>
+ </rdf:li>
+ </rdf:Bag>
+ </s:grapes>
+ </rdf:Description>
+</rdf:RDF>
diff --git a/dom/xul/templates/crashtests/330012-1.xul b/dom/xul/templates/crashtests/330012-1.xul
new file mode 100644
index 000000000..ea797c2cf
--- /dev/null
+++ b/dom/xul/templates/crashtests/330012-1.xul
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <foo id="foo" datasources="330012-1.rdf" ref="urn:root">
+ <template>
+ <rule>
+ <conditions>
+ <content uri="?root"/>
+ <triple subject="?root"
+ predicate="urn:squarefree:a1:grapes"
+ object="?lalala"/>
+ <member container="?grapes" child="?grape"/>
+ </conditions>
+ <action>
+ <bar uri="?grape"/>
+ </action>
+ </rule>
+ </template>
+ </foo>
+
+</window>
diff --git a/dom/xul/templates/crashtests/404346-1.xul b/dom/xul/templates/crashtests/404346-1.xul
new file mode 100644
index 000000000..e947960da
--- /dev/null
+++ b/dom/xul/templates/crashtests/404346-1.xul
@@ -0,0 +1,7 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<box id="b">
+<box id="a" template="b"/>
+<triple/>
+</box>
+<box datasources="" observes="a" ref="bbb"/>
+</window> \ No newline at end of file
diff --git a/dom/xul/templates/crashtests/415019-1.xul b/dom/xul/templates/crashtests/415019-1.xul
new file mode 100644
index 000000000..9fb9560f6
--- /dev/null
+++ b/dom/xul/templates/crashtests/415019-1.xul
@@ -0,0 +1,14 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <hbox datasources="nosuch.rdf" ref="urn:root">
+ <template>
+ <rule>
+ <conditions>
+ <triple subject="?a"/>
+ </conditions>
+ <action>
+ <vbox uri="?b"/>
+ </action>
+ </rule>
+ </template>
+ </hbox>
+</window>
diff --git a/dom/xul/templates/crashtests/417840-1.xul b/dom/xul/templates/crashtests/417840-1.xul
new file mode 100644
index 000000000..e41af81e9
--- /dev/null
+++ b/dom/xul/templates/crashtests/417840-1.xul
@@ -0,0 +1 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="document.getElementById('foo').removeAttribute('ref');"><foo id="foo" datasources="nosuch.rdf" ref="urn:root"><template/></foo></window>
diff --git a/dom/xul/templates/crashtests/424418-1.xul b/dom/xul/templates/crashtests/424418-1.xul
new file mode 100644
index 000000000..d8565643a
--- /dev/null
+++ b/dom/xul/templates/crashtests/424418-1.xul
@@ -0,0 +1 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><hbox datasources="u"/></window>
diff --git a/dom/xul/templates/crashtests/crashtests.list b/dom/xul/templates/crashtests/crashtests.list
new file mode 100644
index 000000000..ed99a3ae8
--- /dev/null
+++ b/dom/xul/templates/crashtests/crashtests.list
@@ -0,0 +1,7 @@
+load 257752-1-recursion.xul
+load 329884-1.xul
+skip-if(winWidget) load 330012-1.xul # bug 742455
+load 404346-1.xul
+load 415019-1.xul
+load 417840-1.xul
+load 424418-1.xul
diff --git a/dom/xul/templates/moz.build b/dom/xul/templates/moz.build
new file mode 100644
index 000000000..3beb0b7e5
--- /dev/null
+++ b/dom/xul/templates/moz.build
@@ -0,0 +1,58 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini']
+
+XPIDL_SOURCES += [
+ 'nsIXULBuilderListener.idl',
+ 'nsIXULSortService.idl',
+ 'nsIXULTemplateBuilder.idl',
+ 'nsIXULTemplateQueryProcessor.idl',
+ 'nsIXULTemplateResult.idl',
+ 'nsIXULTemplateRuleFilter.idl',
+]
+
+XPIDL_MODULE = 'xultmpl'
+
+UNIFIED_SOURCES += [
+ 'nsContentSupportMap.cpp',
+ 'nsContentTestNode.cpp',
+ 'nsInstantiationNode.cpp',
+ 'nsRDFBinding.cpp',
+ 'nsRDFConInstanceTestNode.cpp',
+ 'nsRDFConMemberTestNode.cpp',
+ 'nsRDFPropertyTestNode.cpp',
+ 'nsRDFQuery.cpp',
+ 'nsResourceSet.cpp',
+ 'nsRuleNetwork.cpp',
+ 'nsTemplateMatch.cpp',
+ 'nsTemplateRule.cpp',
+ 'nsTreeRows.cpp',
+ 'nsXMLBinding.cpp',
+ 'nsXULContentBuilder.cpp',
+ 'nsXULContentUtils.cpp',
+ 'nsXULSortService.cpp',
+ 'nsXULTemplateBuilder.cpp',
+ 'nsXULTemplateQueryProcessorRDF.cpp',
+ 'nsXULTemplateQueryProcessorStorage.cpp',
+ 'nsXULTemplateQueryProcessorXML.cpp',
+ 'nsXULTemplateResultRDF.cpp',
+ 'nsXULTemplateResultSetRDF.cpp',
+ 'nsXULTemplateResultStorage.cpp',
+ 'nsXULTemplateResultXML.cpp',
+ 'nsXULTreeBuilder.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/dom/xul',
+ '/layout/xul/tree/',
+]
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/dom/xul/templates/nsContentSupportMap.cpp b/dom/xul/templates/nsContentSupportMap.cpp
new file mode 100644
index 000000000..ec10fde74
--- /dev/null
+++ b/dom/xul/templates/nsContentSupportMap.cpp
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsContentSupportMap.h"
+#include "nsXULElement.h"
+
+void
+nsContentSupportMap::Remove(nsIContent* aElement)
+{
+ nsIContent* child = aElement;
+ do {
+ mMap.Remove(child);
+ child = child->GetNextNode(aElement);
+ } while(child);
+}
+
diff --git a/dom/xul/templates/nsContentSupportMap.h b/dom/xul/templates/nsContentSupportMap.h
new file mode 100644
index 000000000..aef7de83e
--- /dev/null
+++ b/dom/xul/templates/nsContentSupportMap.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsContentSupportMap_h__
+#define nsContentSupportMap_h__
+
+#include "PLDHashTable.h"
+#include "nsTemplateMatch.h"
+
+/**
+ * The nsContentSupportMap maintains a mapping from a "resource element"
+ * in the content tree to the nsTemplateMatch that was used to instantiate it. This
+ * is necessary to allow the XUL content to be built lazily. Specifically,
+ * when building "resumes" on a partially-built content element, the builder
+ * will walk upwards in the content tree to find the first element with an
+ * 'id' attribute. This element is assumed to be the "resource element",
+ * and allows the content builder to access the nsTemplateMatch (variable assignments
+ * and rule information).
+ */
+class nsContentSupportMap {
+public:
+ nsContentSupportMap() : mMap(PLDHashTable::StubOps(), sizeof(Entry)) { }
+ ~nsContentSupportMap() { }
+
+ nsresult Put(nsIContent* aElement, nsTemplateMatch* aMatch) {
+ PLDHashEntryHdr* hdr = mMap.Add(aElement, mozilla::fallible);
+ if (!hdr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ Entry* entry = static_cast<Entry*>(hdr);
+ NS_ASSERTION(entry->mMatch == nullptr, "over-writing entry");
+ entry->mContent = aElement;
+ entry->mMatch = aMatch;
+ return NS_OK;
+ }
+
+ bool Get(nsIContent* aElement, nsTemplateMatch** aMatch) {
+ PLDHashEntryHdr* hdr = mMap.Search(aElement);
+ if (!hdr)
+ return false;
+
+ Entry* entry = static_cast<Entry*>(hdr);
+ *aMatch = entry->mMatch;
+ return true;
+ }
+
+ void Remove(nsIContent* aElement);
+
+ void Clear() { mMap.Clear(); }
+
+protected:
+ PLDHashTable mMap;
+
+ struct Entry : public PLDHashEntryHdr {
+ nsIContent* mContent;
+ nsTemplateMatch* mMatch;
+ };
+};
+
+#endif
diff --git a/dom/xul/templates/nsContentTestNode.cpp b/dom/xul/templates/nsContentTestNode.cpp
new file mode 100644
index 000000000..53253a304
--- /dev/null
+++ b/dom/xul/templates/nsContentTestNode.cpp
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsContentTestNode.h"
+#include "nsIRDFResource.h"
+#include "nsIAtom.h"
+#include "nsIDOMElement.h"
+#include "nsXULContentUtils.h"
+#include "nsIXULTemplateResult.h"
+#include "nsIXULTemplateBuilder.h"
+#include "nsXULTemplateQueryProcessorRDF.h"
+
+#include "mozilla/Logging.h"
+
+using mozilla::LogLevel;
+
+extern mozilla::LazyLogModule gXULTemplateLog;
+
+nsContentTestNode::nsContentTestNode(nsXULTemplateQueryProcessorRDF* aProcessor,
+ nsIAtom* aRefVariable)
+ : TestNode(nullptr),
+ mProcessor(aProcessor),
+ mDocument(nullptr),
+ mRefVariable(aRefVariable),
+ mTag(nullptr)
+{
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ nsAutoString tag(NS_LITERAL_STRING("(none)"));
+ if (mTag)
+ mTag->ToString(tag);
+
+ nsAutoString refvar(NS_LITERAL_STRING("(none)"));
+ if (aRefVariable)
+ aRefVariable->ToString(refvar);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsContentTestNode[%p]: ref-var=%s tag=%s",
+ this, NS_ConvertUTF16toUTF8(refvar).get(),
+ NS_ConvertUTF16toUTF8(tag).get()));
+ }
+}
+
+nsresult
+nsContentTestNode::FilterInstantiations(InstantiationSet& aInstantiations,
+ bool* aCantHandleYet) const
+
+{
+ if (aCantHandleYet)
+ *aCantHandleYet = false;
+ return NS_OK;
+}
+
+nsresult
+nsContentTestNode::Constrain(InstantiationSet& aInstantiations)
+{
+ // contrain the matches to those that have matched in the template builder
+
+ nsIXULTemplateBuilder* builder = mProcessor->GetBuilder();
+ if (!builder) {
+ aInstantiations.Clear();
+ return NS_OK;
+ }
+
+ nsresult rv;
+
+ InstantiationSet::Iterator last = aInstantiations.Last();
+ for (InstantiationSet::Iterator inst = aInstantiations.First(); inst != last; ++inst) {
+
+ nsCOMPtr<nsIRDFNode> refValue;
+ bool hasRefBinding = inst->mAssignments.GetAssignmentFor(mRefVariable,
+ getter_AddRefs(refValue));
+ if (hasRefBinding) {
+ nsCOMPtr<nsIRDFResource> refResource = do_QueryInterface(refValue);
+ if (refResource) {
+ bool generated;
+ rv = builder->HasGeneratedContent(refResource, mTag, &generated);
+ if (NS_FAILED(rv)) return rv;
+
+ if (generated)
+ continue;
+ }
+ }
+
+ aInstantiations.Erase(inst--);
+ }
+
+ return NS_OK;
+}
diff --git a/dom/xul/templates/nsContentTestNode.h b/dom/xul/templates/nsContentTestNode.h
new file mode 100644
index 000000000..ebea5bcf6
--- /dev/null
+++ b/dom/xul/templates/nsContentTestNode.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsContentTestNode_h__
+#define nsContentTestNode_h__
+
+#include "mozilla/Attributes.h"
+#include "nscore.h"
+#include "nsRuleNetwork.h"
+#include "nsIAtom.h"
+#include "nsIDOMDocument.h"
+
+class nsXULTemplateQueryProcessorRDF;
+
+/**
+ * The nsContentTestNode is always the top node in a query's rule network. It
+ * exists so that Constrain can filter out resources that aren't part of a
+ * result.
+ */
+class nsContentTestNode : public TestNode
+{
+public:
+ nsContentTestNode(nsXULTemplateQueryProcessorRDF* aProcessor,
+ nsIAtom* aContentVariable);
+
+ virtual nsresult FilterInstantiations(InstantiationSet& aInstantiations,
+ bool* aCantHandleYet) const override;
+
+ nsresult
+ Constrain(InstantiationSet& aInstantiations) override;
+
+ void SetTag(nsIAtom* aTag, nsIDOMDocument* aDocument)
+ {
+ mTag = aTag;
+ mDocument = aDocument;
+ }
+
+protected:
+ nsXULTemplateQueryProcessorRDF *mProcessor;
+ nsIDOMDocument* mDocument;
+ nsCOMPtr<nsIAtom> mRefVariable;
+ nsCOMPtr<nsIAtom> mTag;
+};
+
+#endif // nsContentTestNode_h__
+
diff --git a/dom/xul/templates/nsIXULBuilderListener.idl b/dom/xul/templates/nsIXULBuilderListener.idl
new file mode 100644
index 000000000..33ae2b3e8
--- /dev/null
+++ b/dom/xul/templates/nsIXULBuilderListener.idl
@@ -0,0 +1,28 @@
+/* -*- Mode: idl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsISupports.idl"
+
+interface nsIXULTemplateBuilder;
+
+// An nsIXULBuilderListener object is a listener that will be notified
+// when a template builder rebuilds its content.
+[scriptable, uuid(ac46be8f-c863-4c23-84a2-d0fcc8dfa9f4)]
+interface nsIXULBuilderListener: nsISupports {
+
+ /**
+ * Called before a template builder rebuilds its content.
+ * @param aBuilder the template builder that rebuilds the content.
+ */
+ void willRebuild(in nsIXULTemplateBuilder aBuilder);
+
+ /**
+ * Called after a template builder has rebuilt its content.
+ * @param aBuilder the template builder that has rebuilt the content.
+ */
+ void didRebuild(in nsIXULTemplateBuilder aBuilder);
+
+};
diff --git a/dom/xul/templates/nsIXULSortService.idl b/dom/xul/templates/nsIXULSortService.idl
new file mode 100644
index 000000000..c8eb6a8bb
--- /dev/null
+++ b/dom/xul/templates/nsIXULSortService.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMNode;
+
+/**
+ * A service used to sort the contents of a XUL widget.
+ */
+[scriptable, uuid(F29270C8-3BE5-4046-9B57-945A84DFF132)]
+interface nsIXULSortService : nsISupports
+{
+ const unsigned long SORT_COMPARECASE = 0x0001;
+ const unsigned long SORT_INTEGER = 0x0100;
+
+ /**
+ * Sort the contents of the widget containing <code>aNode</code>
+ * using <code>aSortKey</code> as the comparison key, and
+ * <code>aSortDirection</code> as the direction.
+ *
+ * @param aNode A node in the XUL widget whose children are to be sorted.
+ * @param aSortKey The value to be used as the comparison key.
+ * @param aSortHints One or more hints as to how to sort:
+ *
+ * ascending: to sort the contents in ascending order
+ * descending: to sort the contents in descending order
+ * comparecase: perform case sensitive comparisons
+ * integer: treat values as integers, non-integers are compared as strings
+ * twostate: don't allow the natural (unordered state)
+ */
+ void sort(in nsIDOMNode aNode,
+ in AString aSortKey,
+ in AString aSortHints);
+};
+
+%{C++
+nsresult
+NS_NewXULSortService(nsIXULSortService **result);
+%}
diff --git a/dom/xul/templates/nsIXULTemplateBuilder.idl b/dom/xul/templates/nsIXULTemplateBuilder.idl
new file mode 100644
index 000000000..755b57e57
--- /dev/null
+++ b/dom/xul/templates/nsIXULTemplateBuilder.idl
@@ -0,0 +1,409 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "domstubs.idl"
+
+interface nsIAtom;
+interface nsIContent;
+interface nsIXULBuilderListener;
+interface nsIXULTemplateResult;
+interface nsIXULTemplateRuleFilter;
+interface nsIXULTemplateQueryProcessor;
+interface nsIRDFResource;
+interface nsIRDFCompositeDataSource;
+interface nsIDOMDataTransfer;
+
+/**
+ * A template builder, given an input source of data, a template, and a
+ * reference point, generates a list of results from the input, and copies
+ * part of the template for each result. Templates may generate content
+ * recursively, using the same template, but with the previous iteration's
+ * results as the reference point. As an example, for an XML datasource the
+ * initial reference point would be a specific node in the DOM tree and a
+ * template might generate a list of all child nodes. For the next iteration,
+ * those children would be used to generate output for their child nodes and
+ * so forth.
+ *
+ * A template builder is attached to a single DOM node; this node is called
+ * the root node and is expected to contain a XUL template element as a direct
+ * child. Different template builders may be specialized in the manner in
+ * which they generate and display the resulting content from the template.
+ *
+ * The structure of a template is as follows:
+ *
+ * <rootnode datasources="" ref="">
+ * <template>
+ * <queryset>
+ * <query>
+ * </query>
+ * <rule>
+ * <conditions>...</conditions>
+ * <bindings>...</bindings>
+ * <action>...</action>
+ * </rule>
+ * </queryset>
+ * </template>
+ * </rootnode>
+ *
+ * The datasources attribute on the root node is used to identify the source
+ * of data to be used. The ref attribute is used to specify the reference
+ * point for the query. Currently, the datasource will either be an
+ * nsIRDFDataSource or a DOM node. In the future, other datasource types may
+ * be used.
+ *
+ * The <queryset> element contains a single query and one or more <rule>
+ * elements. There may be more than one <queryset> if multiple queries are
+ * desired, and this element is optional if only one query is needed -- in
+ * that case the <query> and <rule>s are allowed to be children of the
+ * <template> node
+ *
+ * The contents of the query are processed by a separate component called a
+ * query processor. This query processor is expected to use this query to
+ * generate results when asked by the template builder. The template builder
+ * then generates output for each result based on the <rule> elements.
+ *
+ * This allows the query processor to be specific to a particular kind of
+ * input data or query syntax, while the template builder remains independent
+ * of the kind of data being used. Due to this, the query processor will be
+ * supplied with the datasource and query which the template builder handles
+ * in an opaque way, while the query processor handles these more
+ * specifically.
+ *
+ * Results implement the nsIXULTemplateResult interface and may be identified
+ * by an id which must be unique within a given set of query results.
+ *
+ * Each query may be accompanied by one or more <rule> elements. These rules
+ * are evaluated by the template builder for each result produced by the
+ * query. A rule consists of conditions that cause a rule to be either
+ * accepted or rejected. The condition syntax allows for common conditional
+ * handling; additional filtering may be applied by adding a custom filter
+ * to a rule with the builder's addRuleFilter method.
+ *
+ * If a result passes a rule's conditions, this is considered a match, and the
+ * content within the rule's <action> body is inserted as a sibling of the
+ * <template>, assuming the template builder creates real DOM content. Only
+ * one rule will match a result. For a tree builder, for example, the content
+ * within the action body is used to create the tree rows instead. A matching
+ * result must have its ruleMatched method called. When a result no longer
+ * matches, the result's hasBeenRemoved method must be called.
+ *
+ * Optionally, the rule may have a <bindings> section which may be used to
+ * define additional variables to be used within an action body. Each of these
+ * declared bindings must be supplied to the query processor via its
+ * addBinding method. The bindings are evaluated after a rule has matched.
+ *
+ * Templates may generate content recursively, using the previous iteration's
+ * results as reference point to invoke the same queries. Since the reference
+ * point is different, different output will typically be generated.
+ *
+ * The reference point nsIXULTemplateResult object for the first iteration is
+ * determined by calling the query processor's translateRef method using the
+ * value of the root node's ref attribute. This object may be retrieved later
+ * via the builder's rootResult property.
+ *
+ * For convenience, each reference point as well as all results implement the
+ * nsIXULTemplateResult interface, allowing the result objects from each
+ * iteration to be used directly as the reference points for the next
+ * iteration.
+ *
+ * When using multiple queries, each may generate results with the same id.
+ * More than one of these results may match one of the rules in their
+ * respective queries, however only the result for the earliest matching query
+ * in the template becomes the active match and generates output. The
+ * addResult, removeResult, replaceResult and resultBindingChanged methods may
+ * be called by the query processor to indicate that the set of valid results
+ * has changed, such that a different query may match. If a different match
+ * would become active, the content for the existing match is removed and the
+ * content for the new match is generated. A query processor is not required
+ * to provide any support for updating results after they have been generated.
+ *
+ * See http://wiki.mozilla.org/XUL:Templates_Plan for details about templates.
+ */
+[scriptable, uuid(A583B676-5B02-4F9C-A0C9-CB850CB99818)]
+interface nsIXULTemplateBuilder : nsISupports
+{
+ /**
+ * The root node in the DOM to which this builder is attached.
+ */
+ readonly attribute nsIDOMElement root;
+
+ /**
+ * The opaque datasource object that is used for the template. This object
+ * is created by the getDataSource method of the query processor. May be
+ * null if the datasource has not been loaded yet. Set this attribute to
+ * use a different datasource and rebuild the template.
+ *
+ * For an RDF datasource, this will be the same as the database. For XML
+ * this will be the nsIDOMNode for the datasource document or node for
+ * an inline reference (such as #name). Other query processors may use
+ * other types for the datasource.
+ */
+ attribute nsISupports datasource;
+
+ /**
+ * The composite datasource that the template builder observes
+ * and uses to create content. This is used only for RDF queries and is
+ * maintained for backwards compatibility. It will be the same object as
+ * the datasource property. For non-RDF queries, it will always be null.
+ */
+ readonly attribute nsIRDFCompositeDataSource database;
+
+ /**
+ * The virtual result representing the starting reference point,
+ * determined by calling the query processor's translateRef method
+ * with the root node's ref attribute as an argument.
+ */
+ readonly attribute nsIXULTemplateResult rootResult;
+
+ /**
+ * The query processor used to generate results.
+ */
+ [noscript] readonly attribute nsIXULTemplateQueryProcessor queryProcessor;
+
+ /**
+ * Force the template builder to rebuild its content. All existing content
+ * will be removed first. The query processor's done() method will be
+ * invoked during cleanup, followed by its initializeForBuilding method
+ * when the content is to be regenerated.
+ *
+ */
+ void rebuild();
+
+ /**
+ * Reload any of our RDF datasources that support nsIRDFRemoteDatasource.
+ *
+ * @note This is a temporary hack so that remote-XUL authors can
+ * reload remote datasources. When RDF becomes remote-scriptable,
+ * this will no longer be necessary.
+ */
+ void refresh();
+
+ /**
+ * Inform the template builder that a new result is available. The builder
+ * will add this result to the set of results. The query node that the
+ * new result applies to must be specified using the aQueryNode parameter.
+ *
+ * The builder will apply the rules associated with the query to the new
+ * result, unless a result with the same id from an earlier query
+ * supersedes it, and the result's RuleMatched method will be called if it
+ * matches.
+ *
+ * @param aResult the result to add
+ * @param aQueryNode the query that the result applies to
+ *
+ * @throws NS_ERROR_NULL_POINTER if aResult or aQueryNode are null
+ */
+ void addResult(in nsIXULTemplateResult aResult, in nsIDOMNode aQueryNode);
+
+ /**
+ * Inform the template builder that a result no longer applies. The builder
+ * will call the remove content generated for the result, if any. If a different
+ * query would then match instead, it will become the active match. This
+ * method will have no effect if the result isn't known to the builder.
+ *
+ * @param aResult the result to remove
+ *
+ * @throws NS_ERROR_NULL_POINTER if aResult is null
+ */
+ void removeResult(in nsIXULTemplateResult aResult);
+
+ /**
+ * Inform the template builder that one result should be replaced with
+ * another. Both the old result (aOldResult) and the new result
+ * (aNewResult) must have the same id. The query node that the new result
+ * applies to must be specified using the aQueryNode parameter.
+ *
+ * This method is expected to have the same effect as calling both
+ * removeResult for the old result and addResult for the new result.
+ *
+ * @param aOldResult the old result
+ * @param aNewResult the new result
+ * @param aQueryNode the query that the new result applies to
+ *
+ * @throws NS_ERROR_NULL_POINTER if either argument is null, or
+ * NS_ERROR_INVALID_ARG if the ids don't match
+ */
+ void replaceResult(in nsIXULTemplateResult aOldResult,
+ in nsIXULTemplateResult aNewResult,
+ in nsIDOMNode aQueryNode);
+
+ /**
+ * Inform the template builder that one or more of the optional bindings
+ * for a result has changed. In this case, the rules are not reapplied as
+ * it is expected that the same rule will still apply. The builder will
+ * resynchronize any variables that are referenced in the action body.
+ *
+ * @param aResult the result to change
+ *
+ * @throws NS_ERROR_NULL_POINTER if aResult is null
+ */
+ void resultBindingChanged(in nsIXULTemplateResult aResult);
+
+ /**
+ * Return the result for a given id. Only one such result is returned and
+ * is always the result with that id associated with the active match.
+ * This method will return null is there is no result for the id.
+ *
+ * @param aId the id to return the result for
+ */
+ nsIXULTemplateResult getResultForId(in AString aId);
+
+ /**
+ * Retrieve the result corresponding to a generated element, or null is
+ * there isn't one.
+ *
+ * @param aContent element to result the result of
+ */
+ nsIXULTemplateResult getResultForContent(in nsIDOMElement aElement);
+
+ /**
+ * Returns true if the node has content generated for it. This method is
+ * intended to be called only by the RDF query processor. If aTag is set,
+ * the content must have a tag name that matches aTag. aTag may be ignored
+ * for builders that don't generate real DOM content.
+ *
+ * @param aNode node to check
+ * @param aTag tag that must match
+ */
+ boolean hasGeneratedContent(in nsIRDFResource aNode, in nsIAtom aTag);
+
+ /**
+ * Adds a rule filter for a given rule, which may be used for specialized
+ * rule filtering. Any existing filter on the rule is removed. The default
+ * conditions specified inside the <rule> tag are applied before the
+ * rule filter is applied, meaning that the filter may be used to further
+ * filter out results but not reaccept results that have already been
+ * rejected.
+ *
+ * @param aRule the rule to apply the filter to
+ * @param aFilter the filter to add
+ */
+ void addRuleFilter(in nsIDOMNode aRule, in nsIXULTemplateRuleFilter aFilter);
+
+ /**
+ * Called to initialize a XUL content builder on a particular root
+ * element. This element presumably has a ``datasources''
+ * attribute, which the builder will parse to set up the template
+ * builder's datasources.
+ */
+ [noscript] void init(in nsIContent aElement);
+
+ /**
+ * Invoked lazily by a XUL element that needs its child content built.
+ * If aForceCreation is true, then the contents of an element will be
+ * generated even if it is closed. If false, the element will only
+ * generate its contents if it is open. This behaviour is used with menus.
+ */
+ [noscript] void createContents(in nsIContent aElement,
+ in boolean aForceCreation);
+
+ /**
+ * Add a listener to this template builder. The template builder
+ * holds a strong reference to the listener.
+ */
+ void addListener(in nsIXULBuilderListener aListener);
+
+ /**
+ * Remove a listener from this template builder.
+ */
+ void removeListener(in nsIXULBuilderListener aListener);
+};
+
+/**
+ * nsIXULTreeBuilderObserver
+ * This interface allows clients of the XULTreeBuilder to define domain
+ * specific handling of specific nsITreeView methods that
+ * XULTreeBuilder does not implement.
+ */
+[scriptable, uuid(57CED9A7-EC0B-4A0E-8AEB-5DA32EBE951C)]
+interface nsIXULTreeBuilderObserver : nsISupports
+{
+ const long DROP_BEFORE = -1;
+ const long DROP_ON = 0;
+ const long DROP_AFTER = 1;
+ /**
+ * Methods used by the drag feedback code to determine if a drag is allowable at
+ * the current location. To get the behavior where drops are only allowed on
+ * items, such as the mailNews folder pane, always return false whe
+ * the orientation is not DROP_ON.
+ */
+ boolean canDrop(in long index, in long orientation, in nsIDOMDataTransfer dataTransfer);
+
+ /**
+ * Called when the user drops something on this view. The |orientation| param
+ * specifies before/on/after the given |row|.
+ */
+ void onDrop(in long row, in long orientation, in nsIDOMDataTransfer dataTransfer);
+
+ /**
+ * Called when an item is opened or closed.
+ */
+ void onToggleOpenState (in long index);
+
+ /**
+ * Called when a header is clicked.
+ */
+ void onCycleHeader(in wstring colID, in nsIDOMElement elt);
+
+ /**
+ * Called when a cell in a non-selectable cycling column (e.g.
+ * unread/flag/etc.) is clicked.
+ */
+ void onCycleCell(in long row, in wstring colID);
+
+ /**
+ * Called when selection in the tree changes
+ */
+ void onSelectionChanged();
+
+ /**
+ * A command API that can be used to invoke commands on the selection.
+ * The tree will automatically invoke this method when certain keys
+ * are pressed. For example, when the DEL key is pressed, performAction
+ * will be called with the "delete" string.
+ */
+ void onPerformAction(in wstring action);
+
+ /**
+ * A command API that can be used to invoke commands on a specific row.
+ */
+ void onPerformActionOnRow(in wstring action, in long row);
+
+ /**
+ * A command API that can be used to invoke commands on a specific cell.
+ */
+ void onPerformActionOnCell(in wstring action, in long row, in wstring colID);
+};
+
+[scriptable, uuid(06b31b15-ebf5-4e74-a0e2-6bc0a18a3969)]
+interface nsIXULTreeBuilder : nsISupports
+{
+ /**
+ * Retrieve the RDF resource associated with the specified row.
+ */
+ nsIRDFResource getResourceAtIndex(in long aRowIndex);
+
+ /**
+ * Retrieve the index associated with specified RDF resource.
+ */
+ long getIndexOfResource(in nsIRDFResource resource);
+
+ /**
+ * Add a Tree Builder Observer to handle Tree View
+ * methods that the base builder does not implement.
+ */
+ void addObserver(in nsIXULTreeBuilderObserver aObserver);
+
+ /**
+ * Remove an Tree Builder Observer.
+ */
+ void removeObserver(in nsIXULTreeBuilderObserver aObserver);
+
+ /**
+ * Sort the contents of the tree using the specified column.
+ */
+ void sort(in nsIDOMElement aColumnElement);
+};
+
diff --git a/dom/xul/templates/nsIXULTemplateQueryProcessor.idl b/dom/xul/templates/nsIXULTemplateQueryProcessor.idl
new file mode 100644
index 000000000..e064cf3b8
--- /dev/null
+++ b/dom/xul/templates/nsIXULTemplateQueryProcessor.idl
@@ -0,0 +1,276 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "domstubs.idl"
+
+interface nsIAtom;
+interface nsIArray;
+interface nsISimpleEnumerator;
+interface nsIXULTemplateResult;
+interface nsIXULTemplateRuleFilter;
+interface nsIXULTemplateBuilder;
+
+/**
+ * A query processor takes a template query and generates results for it given
+ * a datasource and a reference point. There is a one-to-one relationship
+ * between a template builder and a query processor. The template builder
+ * creates the query processor, and there is no other means to retrieve it.
+ *
+ * A template query is the contents inside a <query> element within the
+ * template. The actual syntax is opaque to the template builder and defined
+ * by a query processor. The query is expected to consist of either text or
+ * DOM nodes that, when executed by a call to the generateResults method, will
+ * allow the generation of a list of results.
+ *
+ * The template builder will supply two variables, the reference variable and
+ * the member variable to further indicate what part of the datasource is to
+ * be examined in addition to the query itself. The reference is always
+ * a placeholder for the starting point and the member is always a placeholder
+ * for the end points (the results).
+ *
+ * The reference point is important when generating output recursively, as
+ * the query will be the same for each iteration, however, the reference point
+ * will differ.
+ *
+ * For instance, when examining an XML source, an XML query processor might
+ * begin at the node referred by the reference variable and end at a list of
+ * that node's children.
+ *
+ * Some queries may not need the reference variable if the syntax or the form
+ * of the data implies the value. For instance, a datasource that holds a
+ * table that can only produce one set of results.
+ *
+ * The reference variable may be specified in a template by setting the
+ * "container" attribute on the <template> element to the variable to use. The
+ * member variable may be specified in a similar way using the "member"
+ * attribute, or it may be specified in the first <action> body in the
+ * template as the value of a uri attribute on an element. A breadth-first
+ * search of the first action is performed to find this element.
+ *
+ * If unspecified, the default value of the reference variable is ?uri.
+ *
+ * For example, a query might have the following syntax:
+ *
+ * (?id, ?name, ?url) from Bookmarks where parentfolder = ?start
+ *
+ * This query might generate a result for each bookmark within a given folder.
+ * The variable ?start would be the reference variable, while the variable ?id
+ * would be the member variable, since it is the unique value that identifies
+ * a result. Each result will have the four variables referred to defined for
+ * it and the values may be retrieved using the result's getBindingFor and
+ * getBindingObjectFor methods.
+ *
+ * The template builder must call initializeForBuilding before the other
+ * methods, except for translateRef. The builder will then call compileQuery
+ * for each query in the template to compile the queries. When results need
+ * to be generated, the builder will call generateResults. The
+ * initializeForBuilding, compileQuery and addBinding methods may not be
+ * called after generateResults has been called until the builder indicates
+ * that the generated output is being removed by calling the done method.
+ *
+ * Currently, the datasource supplied to the methods will always be an
+ * nsIRDFDataSource or a DOM node, and will always be the same one in between
+ * calls to initializeForBuilding and done.
+ */
+[scriptable, uuid(C257573F-444F-468A-BA27-DE979DC55FE4)]
+interface nsIXULTemplateQueryProcessor : nsISupports
+{
+ /**
+ * Retrieve the datasource to use for the query processor. The list of
+ * datasources in a template is specified using the datasources attribute as
+ * a space separated list of URIs. This list is processed by the builder and
+ * supplied to the query processor in the aDataSources array as a list of
+ * nsIURI objects or nsIDOMNode objects. This method may return an object
+ * corresponding to these URIs and the builder will supply this object to
+ * other query processor methods. For example, for an XML source, the
+ * datasource might be an nsIDOMNode.
+ *
+ * All of these URIs are checked by the builder so it is safe to use them,
+ * however note that a URI that redirects may still needs to be checked to
+ * ensure that the document containing aRootNode may access it. This is the
+ * responsibility of the query processor if it needs to load the content of
+ * the URI.
+ *
+ * If the query processor needs to load the datasource asynchronously, it
+ * may set the aShouldDelayBuilding returned parameter to true to delay
+ * building the template content, and call the builder's Rebuild method when
+ * the data is available.
+ *
+ * @param aDataSources the list of nsIURI objects and/or nsIDOMNode objects
+ * @param aRootNode the root node the builder is attached to
+ * @param aIsTrusted true if the template is in a trusted document
+ * @param aBuilder the template builder
+ * @param aShouldDelayBuilding [out] whether the builder should wait to
+ * build the content or not
+ * @returns a datasource object
+ */
+ nsISupports getDatasource(in nsIArray aDataSources,
+ in nsIDOMNode aRootNode,
+ in boolean aIsTrusted,
+ in nsIXULTemplateBuilder aBuilder,
+ out boolean aShouldDelayBuilding);
+
+ /**
+ * Initialize for query generation. This will be called before the rules are
+ * processed and whenever the template is rebuilt. This method must be
+ * called once before any of the other query processor methods except for
+ * translateRef.
+ *
+ * @param aDatasource datasource for the data
+ * @param aBuilder the template builder
+ * @param aRootNode the root node the builder is attached to
+ *
+ * @throws NS_ERROR_INVALID_ARG if the datasource is not supported or
+ * NS_ERROR_UNEXPECTED if generateResults has already been called.
+ */
+ void initializeForBuilding(in nsISupports aDatasource,
+ in nsIXULTemplateBuilder aBuilder,
+ in nsIDOMNode aRootNode);
+
+ /**
+ * Called when the template builder is being destroyed so that the query
+ * processor can clean up any state. The query processor should remove as
+ * much state as possible, such as results or references to the builder.
+ * This method will also be called when the template is going to be rebuilt.
+ */
+ void done();
+
+ /**
+ * Compile a query from a node. The result of this function will later be
+ * passed to generateResults for result generation. If null is returned,
+ * the query will be ignored.
+ *
+ * The template builder will call this method once for each query within
+ * the template, before any results can be generated using generateResults,
+ * but after initializeForBuilding has been called. This method should not
+ * be called again for the same query unless the template is rebuilt.
+ *
+ * The reference variable may be used by the query processor as a
+ * placeholder for the reference point, or starting point in the query.
+ *
+ * The member variable is determined from the member attribute on the
+ * template, or from the uri in the first action's rule if that attribute is
+ * not present. A rule processor may use the member variable as a hint to
+ * indicate what variable is expected to contain the results.
+ *
+ * @param aBuilder the template builder
+ * @param aQuery <query> node to compile
+ * @param aRefVariable the reference variable
+ * @param aMemberVariable the member variable
+ *
+ * @returns a compiled query object
+ */
+ nsISupports compileQuery(in nsIXULTemplateBuilder aBuilder,
+ in nsIDOMNode aQuery,
+ in nsIAtom aRefVariable,
+ in nsIAtom aMemberVariable);
+
+ /**
+ * Generate the results of a query and return them in an enumerator. The
+ * enumerator must contain nsIXULTemplateResult objects. If there are no
+ * results, an empty enumerator must be returned.
+ *
+ * The datasource will be the same as the one passed to the earlier
+ * initializeForBuilding method. The context reference (aRef) is a reference
+ * point used when calculating results.
+ *
+ * The value of aQuery must be the result of a previous call to compileQuery
+ * from this query processor. This method may be called multiple times,
+ * typically with different values for aRef.
+ *
+ * @param aDatasource datasource for the data
+ * @param aRef context reference value used as a starting point
+ * @param aQuery the compiled query returned from query compilation
+ *
+ * @returns an enumerator of nsIXULTemplateResult objects as the results
+ *
+ * @throws NS_ERROR_INVALID_ARG if aQuery is invalid
+ */
+ nsISimpleEnumerator generateResults(in nsISupports aDatasource,
+ in nsIXULTemplateResult aRef,
+ in nsISupports aQuery);
+
+ /**
+ * Add a variable binding for a particular rule. A binding allows an
+ * additional variable to be set for a result, outside of those defined
+ * within the query. These bindings are always optional, in that they will
+ * never affect the results generated.
+ *
+ * This function will never be called after generateResults. Any bindings
+ * that were added should be applied to each result when the result's
+ * ruleMatched method is called, since the bindings are different for each
+ * rule.
+ *
+ * The reference aRef may be used to determine the reference when
+ * calculating the value for the binding, for example when a value should
+ * depend on the value of another variable.
+ *
+ * The syntax of the expression aExpr is defined by the query processor. If
+ * the syntax is invalid, the binding should be ignored. Only fatal errors
+ * should be thrown, or NS_ERROR_UNEXPECTED if generateResults has already
+ * been called.
+ *
+ * As an example, if the reference aRef is the variable '?count' which
+ * holds the value 5, and the expression aExpr is the string '+2', the value
+ * of the variable aVar would be 7, assuming the query processor considers
+ * the syntax '+2' to mean add two to the reference.
+ *
+ * @param aRuleNode rule to add the binding to
+ * @param aVar variable that will be bound
+ * @param aRef variable that holds reference value
+ * @param aExpr expression used to compute the value to assign
+ */
+ void addBinding(in nsIDOMNode aRuleNode,
+ in nsIAtom aVar,
+ in nsIAtom aRef,
+ in AString aExpr);
+
+ /**
+ * Translate a ref attribute string into a result. This is used as the
+ * reference point by the template builder when generating the first level
+ * of content. For recursive generation, the result from the parent
+ * generation phase will be used directly as the reference so a translation
+ * is not needed. This allows all levels to be generated using objects that
+ * all implement the nsIXULTemplateResult interface.
+ *
+ * This method may be called before initializeForBuilding, so the
+ * implementation may use the supplied datasource if it is needed to
+ * translate the reference.
+ *
+ * @param aDatasource datasource for the data
+ * @param aRefString the ref attribute string
+ *
+ * @return the translated ref
+ */
+ nsIXULTemplateResult translateRef(in nsISupports aDatasource,
+ in AString aRefString);
+
+ /**
+ * Compare two results to determine their order, used when sorting results.
+ * This method should return -1 when the left result is less than the right,
+ * 0 if both are equivalent, and 1 if the left is greater than the right.
+ * The comparison should only consider the values for the specified
+ * variable.
+ *
+ * If the comparison variable is null, the results may be
+ * sorted in a natural order, for instance, based on the order the data in
+ * stored in the datasource.
+ *
+ * The sort hints are the flags in nsIXULSortService.
+ *
+ * This method must only be called with results that were created by this
+ * query processor.
+ *
+ * @param aLeft the left result to compare
+ * @param aRight the right result to compare
+ * @param aVar variable to compare
+ *
+ * @param returns -1 if less, 0 if equal, or 1 if greater
+ */
+ int32_t compareResults(in nsIXULTemplateResult aLeft,
+ in nsIXULTemplateResult aRight,
+ in nsIAtom aVar,
+ in unsigned long aSortHints);
+};
diff --git a/dom/xul/templates/nsIXULTemplateResult.idl b/dom/xul/templates/nsIXULTemplateResult.idl
new file mode 100644
index 000000000..6a8ac2439
--- /dev/null
+++ b/dom/xul/templates/nsIXULTemplateResult.idl
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAtom;
+interface nsIDOMNode;
+interface nsIRDFResource;
+
+/**
+ * A single result generated from a template query. Each result is identified
+ * by an id, which must be unique within the set of results produced from a
+ * query. The result may optionally be identified by an RDF resource.
+ *
+ * Generally, the result and its id will be able to uniquely identify a node
+ * in the source data, such as an RDF or XML node. In other contexts, such as
+ * a database query, a result would represent a particular record.
+ *
+ * A result is expected to only be created by a query processor.
+ *
+ * Each result also contains a set of variable bindings. The value for a
+ * particular variable may be retrieved using the getBindingFor and
+ * getBindingObjectFor methods.
+ */
+[scriptable, uuid(ebea0230-36fa-41b7-8e31-760806057965)]
+interface nsIXULTemplateResult : nsISupports
+{
+ /**
+ * True if the result represents a container.
+ */
+ readonly attribute boolean isContainer;
+
+ /**
+ * True if the result represents an empty container.
+ */
+ readonly attribute boolean isEmpty;
+
+ /**
+ * True if the template builder may use this result as the reference point
+ * for additional recursive processing of the template. The template builder
+ * will reprocess the template using this result as the reference point and
+ * generate output content that is expected to be inserted as children of the
+ * output generated for this result. If false, child content is not
+ * processed. This property identifies only the default handling and may be
+ * overriden by syntax used in the template.
+ */
+ readonly attribute boolean mayProcessChildren;
+
+ /**
+ * ID of the result. The DOM element created for this result, if any, will
+ * have its id attribute set to this value. The id must be unique for a
+ * query.
+ */
+ readonly attribute AString id;
+
+ /**
+ * Resource for the result, which may be null. If set, the resource uri
+ * must be the same as the ID property.
+ */
+ readonly attribute nsIRDFResource resource;
+
+ /**
+ * The type of the object. The predefined value 'separator' may be used
+ * for separators. Other values may be used for application specific
+ * purposes.
+ */
+ readonly attribute AString type;
+
+ /**
+ * Get the string representation of the value of a variable for this
+ * result. This string will be used in the action body from a template as
+ * the replacement text. For instance, if the text ?name appears in an
+ * attribute within the action body, it will be replaced with the result
+ * of this method. The question mark is considered part of the variable
+ * name, thus aVar should be ?name and not simply name.
+ *
+ * @param aVar the variable to look up
+ *
+ * @return the value for the variable or a null string if it has no value
+ */
+ AString getBindingFor(in nsIAtom aVar);
+
+ /**
+ * Get an object value for a variable such as ?name for this result.
+ *
+ * This method may return null for a variable, even if getBindingFor returns
+ * a non-null value for the same variable. This method is provided as a
+ * convenience when sorting results.
+ *
+ * @param aVar the variable to look up
+ *
+ * @return the value for the variable or null if it has no value
+ */
+ nsISupports getBindingObjectFor(in nsIAtom aVar);
+
+ /**
+ * Indicate that a particular rule of a query has matched and that output
+ * will be generated for it. Both the query as compiled by the query
+ * processor's compileQuery method and the XUL <rule> element are supplied.
+ * The query must always be one that was compiled by the query processor
+ * that created this result. The <rule> element must always be a child of
+ * the <query> element that was used to compile the query.
+ *
+ * @param aQuery the query that matched
+ * @param aRuleNode the rule node that matched
+ */
+ void ruleMatched(in nsISupports aQuery, in nsIDOMNode aRuleNode);
+
+ /**
+ * Indicate that the output for a result has beeen removed and that the
+ * result is no longer being used by the builder.
+ */
+ void hasBeenRemoved();
+};
diff --git a/dom/xul/templates/nsIXULTemplateRuleFilter.idl b/dom/xul/templates/nsIXULTemplateRuleFilter.idl
new file mode 100644
index 000000000..59c88e072
--- /dev/null
+++ b/dom/xul/templates/nsIXULTemplateRuleFilter.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "domstubs.idl"
+
+interface nsISupports;
+interface nsIXULTemplateResult;
+
+/**
+ * A rule filter may be used to add additional filtering of results to a rule.
+ * The filter is used to further reject results from matching the template's
+ * rules, beyond what the template syntax can do itself, thus allowing for
+ * more complex result filtering. The rule filter is applied after the rule
+ * syntax within the template.
+ *
+ * Only one filter may apply to each rule within the template and may be
+ * assigned using the template builder's addRuleFilter method.
+ */
+[scriptable, uuid(819cd1ed-8010-42e1-a8b9-778b726a1ff3)]
+interface nsIXULTemplateRuleFilter : nsISupports
+{
+ /**
+ * Evaluate a result and return true if the result is accepted by this
+ * filter, or false if it is rejected. Accepted results will have output
+ * generated for them for the rule. Rejected results will not, but they
+ * may still match another rule.
+ *
+ * @param aRef the result to examine
+ * @param aRule the rule node
+ *
+ * @return true if the rule matches
+ */
+ boolean match(in nsIXULTemplateResult aRef, in nsIDOMNode aRule);
+};
diff --git a/dom/xul/templates/nsInstantiationNode.cpp b/dom/xul/templates/nsInstantiationNode.cpp
new file mode 100644
index 000000000..9079d4189
--- /dev/null
+++ b/dom/xul/templates/nsInstantiationNode.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsInstantiationNode.h"
+#include "nsTemplateRule.h"
+#include "nsXULTemplateQueryProcessorRDF.h"
+
+#include "mozilla/Logging.h"
+extern mozilla::LazyLogModule gXULTemplateLog;
+
+nsInstantiationNode::nsInstantiationNode(nsXULTemplateQueryProcessorRDF* aProcessor,
+ nsRDFQuery* aQuery)
+ : mProcessor(aProcessor),
+ mQuery(aQuery)
+{
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsInstantiationNode[%p] query=%p", this, aQuery));
+
+ MOZ_COUNT_CTOR(nsInstantiationNode);
+}
+
+
+nsInstantiationNode::~nsInstantiationNode()
+{
+ MOZ_COUNT_DTOR(nsInstantiationNode);
+}
+
+nsresult
+nsInstantiationNode::Propagate(InstantiationSet& aInstantiations,
+ bool aIsUpdate, bool& aTakenInstantiations)
+{
+ // In update mode, iterate through the results and call the template
+ // builder to update them. In non-update mode, cache them in the processor
+ // to be used during processing. The results are cached in the processor
+ // so that the simple rules are only computed once. In this situation, all
+ // data for all queries are calculated at once.
+ nsresult rv = NS_OK;
+
+ aTakenInstantiations = false;
+
+ if (aIsUpdate) {
+ // Iterate through newly added keys to determine which rules fired.
+ //
+ // XXXwaterson Unfortunately, this could also lead to retractions;
+ // e.g., (container ?a ^empty false) could become "unmatched". How
+ // to track those?
+ nsCOMPtr<nsIDOMNode> querynode;
+ mQuery->GetQueryNode(getter_AddRefs(querynode));
+
+ InstantiationSet::ConstIterator last = aInstantiations.Last();
+ for (InstantiationSet::ConstIterator inst = aInstantiations.First(); inst != last; ++inst) {
+ nsAssignmentSet assignments = inst->mAssignments;
+
+ nsCOMPtr<nsIRDFNode> node;
+ assignments.GetAssignmentFor(mQuery->mMemberVariable,
+ getter_AddRefs(node));
+ if (node) {
+ nsCOMPtr<nsIRDFResource> resource = do_QueryInterface(node);
+ if (resource) {
+ RefPtr<nsXULTemplateResultRDF> nextresult =
+ new nsXULTemplateResultRDF(mQuery, *inst, resource);
+ if (! nextresult)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = mProcessor->AddMemoryElements(*inst, nextresult);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mProcessor->GetBuilder()->AddResult(nextresult, querynode);
+ }
+ }
+ }
+ }
+ else {
+ nsresult rv = mQuery->SetCachedResults(mProcessor, aInstantiations);
+ if (NS_SUCCEEDED(rv))
+ aTakenInstantiations = true;
+ }
+
+ return rv;
+}
diff --git a/dom/xul/templates/nsInstantiationNode.h b/dom/xul/templates/nsInstantiationNode.h
new file mode 100644
index 000000000..5b9c7b9f1
--- /dev/null
+++ b/dom/xul/templates/nsInstantiationNode.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsInstantiationNode_h__
+#define nsInstantiationNode_h__
+
+#include "mozilla/Attributes.h"
+#include "nsRuleNetwork.h"
+#include "nsRDFQuery.h"
+
+class nsXULTemplateQueryProcessorRDF;
+
+/**
+ * A leaf-level node in the rule network. If any instantiations
+ * propagate to this node, then we know we've matched a rule.
+ */
+class nsInstantiationNode : public ReteNode
+{
+public:
+ nsInstantiationNode(nsXULTemplateQueryProcessorRDF* aProcessor,
+ nsRDFQuery* aRule);
+
+ ~nsInstantiationNode();
+
+ // "downward" propagations
+ virtual nsresult Propagate(InstantiationSet& aInstantiations,
+ bool aIsUpdate, bool& aMatched) override;
+
+protected:
+
+ nsXULTemplateQueryProcessorRDF* mProcessor;
+ nsRDFQuery* mQuery;
+};
+
+#endif // nsInstantiationNode_h__
diff --git a/dom/xul/templates/nsRDFBinding.cpp b/dom/xul/templates/nsRDFBinding.cpp
new file mode 100644
index 000000000..120adfa79
--- /dev/null
+++ b/dom/xul/templates/nsRDFBinding.cpp
@@ -0,0 +1,265 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsXULTemplateQueryProcessorRDF.h"
+#include "nsXULTemplateResultRDF.h"
+#include "nsRDFBinding.h"
+
+#ifdef DEBUG
+#include "nsXULContentUtils.h"
+#endif
+
+RDFBindingSet::~RDFBindingSet()
+{
+ while (mFirst) {
+ RDFBinding* doomed = mFirst;
+ mFirst = mFirst->mNext;
+ delete doomed;
+ }
+
+ MOZ_COUNT_DTOR(RDFBindingSet);
+}
+
+nsresult
+RDFBindingSet::AddBinding(nsIAtom* aVar, nsIAtom* aRef, nsIRDFResource* aPredicate)
+{
+ RDFBinding* newbinding = new RDFBinding(aRef, aPredicate, aVar);
+ if (mFirst) {
+ RDFBinding* binding = mFirst;
+
+ while (binding) {
+ // the binding is dependant on the calculation of a previous binding
+ if (binding->mSubjectVariable == aVar)
+ newbinding->mHasDependency = true;
+
+ // if the target variable is already used in a binding, ignore it
+ // since it won't be useful for anything
+ if (binding->mTargetVariable == aVar) {
+ delete newbinding;
+ return NS_OK;
+ }
+
+ // add the binding at the end of the list
+ if (! binding->mNext) {
+ binding->mNext = newbinding;
+ break;
+ }
+
+ binding = binding->mNext;
+ }
+ }
+ else {
+ mFirst = newbinding;
+ }
+
+ mCount++;
+
+ return NS_OK;
+}
+
+bool
+RDFBindingSet::SyncAssignments(nsIRDFResource* aSubject,
+ nsIRDFResource* aPredicate,
+ nsIRDFNode* aTarget,
+ nsIAtom* aMemberVariable,
+ nsXULTemplateResultRDF* aResult,
+ nsBindingValues& aBindingValues)
+{
+ NS_ASSERTION(aBindingValues.GetBindingSet() == this,
+ "nsBindingValues not for this RDFBindingSet");
+ NS_PRECONDITION(aResult, "Must have result");
+
+ bool needSync = false;
+ nsCOMPtr<nsIRDFNode>* valuesArray = aBindingValues.ValuesArray();
+ if (!valuesArray)
+ return false;
+
+ RDFBinding* binding = mFirst;
+ int32_t count = 0;
+
+ // QI for proper comparisons just to be safe
+ nsCOMPtr<nsIRDFNode> subjectnode = do_QueryInterface(aSubject);
+
+ // iterate through the bindings looking for ones that would match the RDF
+ // nodes that were involved in a change
+ nsCOMPtr<nsIRDFNode> value;
+ while (binding) {
+ if (aPredicate == binding->mPredicate) {
+ // if the source of the binding is the member variable, optimize
+ if (binding->mSubjectVariable == aMemberVariable) {
+ valuesArray[count] = aTarget;
+ needSync = true;
+ }
+ else {
+ aResult->GetAssignment(binding->mSubjectVariable, getter_AddRefs(value));
+ if (value == subjectnode) {
+ valuesArray[count] = aTarget;
+ needSync = true;
+ }
+ }
+ }
+
+ binding = binding->mNext;
+ count++;
+ }
+
+ return needSync;
+}
+
+void
+RDFBindingSet::AddDependencies(nsIRDFResource* aSubject,
+ nsXULTemplateResultRDF* aResult)
+{
+ NS_PRECONDITION(aResult, "Must have result");
+
+ // iterate through the bindings and add binding dependencies to the
+ // processor
+
+ nsXULTemplateQueryProcessorRDF* processor = aResult->GetProcessor();
+ if (! processor)
+ return;
+
+ nsCOMPtr<nsIRDFNode> value;
+
+ RDFBinding* binding = mFirst;
+ while (binding) {
+ aResult->GetAssignment(binding->mSubjectVariable, getter_AddRefs(value));
+
+ nsCOMPtr<nsIRDFResource> valueres = do_QueryInterface(value);
+ if (valueres)
+ processor->AddBindingDependency(aResult, valueres);
+
+ binding = binding->mNext;
+ }
+}
+
+void
+RDFBindingSet::RemoveDependencies(nsIRDFResource* aSubject,
+ nsXULTemplateResultRDF* aResult)
+{
+ NS_PRECONDITION(aResult, "Must have result");
+
+ // iterate through the bindings and remove binding dependencies from the
+ // processor
+
+ nsXULTemplateQueryProcessorRDF* processor = aResult->GetProcessor();
+ if (! processor)
+ return;
+
+ nsCOMPtr<nsIRDFNode> value;
+
+ RDFBinding* binding = mFirst;
+ while (binding) {
+ aResult->GetAssignment(binding->mSubjectVariable, getter_AddRefs(value));
+
+ nsCOMPtr<nsIRDFResource> valueres = do_QueryInterface(value);
+ if (valueres)
+ processor->RemoveBindingDependency(aResult, valueres);
+
+ binding = binding->mNext;
+ }
+}
+
+int32_t
+RDFBindingSet::LookupTargetIndex(nsIAtom* aTargetVariable, RDFBinding** aBinding)
+{
+ int32_t idx = 0;
+ RDFBinding* binding = mFirst;
+
+ while (binding) {
+ if (binding->mTargetVariable == aTargetVariable) {
+ *aBinding = binding;
+ return idx;
+ }
+ idx++;
+ binding = binding->mNext;
+ }
+
+ return -1;
+}
+
+nsBindingValues::~nsBindingValues()
+{
+ ClearBindingSet();
+ MOZ_COUNT_DTOR(nsBindingValues);
+}
+
+void
+nsBindingValues::ClearBindingSet()
+{
+ if (mBindings && mValues) {
+ delete [] mValues;
+ mValues = nullptr;
+ }
+
+ mBindings = nullptr;
+}
+
+nsresult
+nsBindingValues::SetBindingSet(RDFBindingSet* aBindings)
+{
+ ClearBindingSet();
+
+ int32_t count = aBindings->Count();
+ if (count) {
+ mValues = new nsCOMPtr<nsIRDFNode>[count];
+ mBindings = aBindings;
+ }
+ else {
+ mValues = nullptr;
+ }
+
+ return NS_OK;
+}
+
+void
+nsBindingValues::GetAssignmentFor(nsXULTemplateResultRDF* aResult,
+ nsIAtom* aVar,
+ nsIRDFNode** aValue)
+{
+ *aValue = nullptr;
+
+ // assignments are calculated lazily when asked for. The only issue is
+ // when a binding has no value in the RDF graph, it will be checked again
+ // every time.
+
+ if (mBindings && mValues) {
+ RDFBinding* binding;
+ int32_t idx = mBindings->LookupTargetIndex(aVar, &binding);
+ if (idx >= 0) {
+ *aValue = mValues[idx];
+ if (*aValue) {
+ NS_ADDREF(*aValue);
+ }
+ else {
+ nsXULTemplateQueryProcessorRDF* processor = aResult->GetProcessor();
+ if (! processor)
+ return;
+
+ nsIRDFDataSource* ds = processor->GetDataSource();
+ if (! ds)
+ return;
+
+ nsCOMPtr<nsIRDFNode> subjectValue;
+ aResult->GetAssignment(binding->mSubjectVariable,
+ getter_AddRefs(subjectValue));
+ if (subjectValue) {
+ nsCOMPtr<nsIRDFResource> subject = do_QueryInterface(subjectValue);
+ ds->GetTarget(subject, binding->mPredicate, true, aValue);
+ if (*aValue)
+ mValues[idx] = *aValue;
+ }
+ }
+ }
+ }
+}
+
+void
+nsBindingValues::RemoveDependencies(nsIRDFResource* aSubject,
+ nsXULTemplateResultRDF* aResult)
+{
+ if (mBindings)
+ mBindings->RemoveDependencies(aSubject, aResult);
+}
diff --git a/dom/xul/templates/nsRDFBinding.h b/dom/xul/templates/nsRDFBinding.h
new file mode 100644
index 000000000..92c8b16ca
--- /dev/null
+++ b/dom/xul/templates/nsRDFBinding.h
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsRDFBinding_h__
+#define nsRDFBinding_h__
+
+#include "nsIAtom.h"
+#include "nsIRDFResource.h"
+#include "nsISupportsImpl.h"
+
+class nsXULTemplateResultRDF;
+class nsBindingValues;
+
+/*
+ * Classes related to storing bindings for RDF handling.
+ */
+
+/*
+ * a <binding> descriptors
+ */
+class RDFBinding {
+
+public:
+
+ nsCOMPtr<nsIAtom> mSubjectVariable;
+ nsCOMPtr<nsIRDFResource> mPredicate;
+ nsCOMPtr<nsIAtom> mTargetVariable;
+
+ // indicates whether a binding is dependant on the result from a
+ // previous binding
+ bool mHasDependency;
+
+ RDFBinding* mNext;
+
+private:
+
+ friend class RDFBindingSet;
+
+ RDFBinding(nsIAtom* aSubjectVariable,
+ nsIRDFResource* aPredicate,
+ nsIAtom* aTargetVariable)
+ : mSubjectVariable(aSubjectVariable),
+ mPredicate(aPredicate),
+ mTargetVariable(aTargetVariable),
+ mHasDependency(false),
+ mNext(nullptr)
+ {
+ MOZ_COUNT_CTOR(RDFBinding);
+ }
+
+ ~RDFBinding()
+ {
+ MOZ_COUNT_DTOR(RDFBinding);
+ }
+};
+
+/*
+ * a collection of <binding> descriptors. This object is refcounted by
+ * nsBindingValues objects and the query processor.
+ */
+class RDFBindingSet final
+{
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~RDFBindingSet();
+
+ // the number of bindings
+ int32_t mCount;
+
+ // pointer to the first binding in a linked list
+ RDFBinding* mFirst;
+
+public:
+
+ RDFBindingSet()
+ : mCount(0),
+ mFirst(nullptr)
+ {
+ MOZ_COUNT_CTOR(RDFBindingSet);
+ }
+
+ NS_INLINE_DECL_REFCOUNTING(RDFBindingSet)
+
+ int32_t Count() const { return mCount; }
+
+ /*
+ * Add a binding (aRef -> aPredicate -> aVar) to the set
+ */
+ nsresult
+ AddBinding(nsIAtom* aVar, nsIAtom* aRef, nsIRDFResource* aPredicate);
+
+ /*
+ * Return true if the binding set contains a binding which would cause
+ * the result to need resynchronizing for an RDF triple. The member
+ * variable may be supplied as an optimization since bindings most
+ * commonly use the member variable as the subject. If aMemberVariable
+ * is set, aSubject must be the value of the member variable for the
+ * result. The supplied binding values aBindingValues must be values
+ * using this binding set (that is aBindingValues->GetBindingSet() == this)
+ *
+ * @param aSubject subject of the RDF triple
+ * @param aPredicate predicate of the RDF triple
+ * @param aTarget target of the RDF triple
+ * @param aMemberVariable member variable for the query for the binding
+ * @param aResult result to synchronize
+ * @param aBindingValues the values for the bindings for the result
+ */
+ bool
+ SyncAssignments(nsIRDFResource* aSubject,
+ nsIRDFResource* aPredicate,
+ nsIRDFNode* aTarget,
+ nsIAtom* aMemberVariable,
+ nsXULTemplateResultRDF* aResult,
+ nsBindingValues& aBindingValues);
+
+ /*
+ * The query processor maintains a map of subjects to an array of results.
+ * This is used such that when a new assertion is added to the RDF graph,
+ * the results associated with the subject of that triple may be checked
+ * to see if their bindings have changed. The AddDependencies method adds
+ * these subject dependencies to the map.
+ */
+ void
+ AddDependencies(nsIRDFResource* aSubject,
+ nsXULTemplateResultRDF* aResult);
+
+ /*
+ * Remove the results from the dependencies map when results are deleted.
+ */
+ void
+ RemoveDependencies(nsIRDFResource* aSubject,
+ nsXULTemplateResultRDF* aResult);
+
+ /*
+ * The nsBindingValues classes stores an array of values, one for each
+ * target symbol that could be set by the bindings in the set.
+ * LookupTargetIndex determines the index into the array for a given
+ * target symbol.
+ */
+ int32_t
+ LookupTargetIndex(nsIAtom* aTargetVariable, RDFBinding** aBinding);
+};
+
+/*
+ * A set of values of bindings. This object is used once per result.
+ * This stores a reference to the binding set and an array of node values.
+ * Since the binding set is used once per query and the values are
+ * used once per result, we reduce size by only storing the value array's
+ * length in the binding set. This is possible since the array is always
+ * a fixed length for a particular binding set.
+ *
+ * XXX ndeakin We may want to revisit this later since it makes the code
+ * more complicated.
+ */
+class nsBindingValues
+{
+protected:
+
+ // the binding set
+ RefPtr<RDFBindingSet> mBindings;
+
+ /*
+ * A set of values for variable bindings. To look up a binding value,
+ * scan through the binding set in mBindings for the right target atom.
+ * Its index will correspond to the index in this array. The size of this
+ * array is determined by the RDFBindingSet's Count().
+ */
+ nsCOMPtr<nsIRDFNode>* mValues;
+
+public:
+
+ nsBindingValues()
+ : mBindings(nullptr),
+ mValues(nullptr)
+ {
+ MOZ_COUNT_CTOR(nsBindingValues);
+ }
+
+ ~nsBindingValues();
+
+
+ /**
+ * Clear the binding set, to be called when the nsBindingValues is deleted
+ * or a new binding set is being set.
+ */
+ void ClearBindingSet();
+
+ RDFBindingSet* GetBindingSet() { return mBindings; }
+
+ /**
+ * Set the binding set to use. This needs to be called once a rule matches
+ * since it is then known which bindings will apply.
+ */
+ nsresult SetBindingSet(RDFBindingSet* aBindings);
+
+ nsCOMPtr<nsIRDFNode>* ValuesArray() { return mValues; }
+
+ /*
+ * Retrieve the assignment for a particular variable
+ */
+ void
+ GetAssignmentFor(nsXULTemplateResultRDF* aResult,
+ nsIAtom* aVar,
+ nsIRDFNode** aValue);
+
+ /*
+ * Remove depenedencies the bindings have on particular resources
+ */
+ void
+ RemoveDependencies(nsIRDFResource* aSubject,
+ nsXULTemplateResultRDF* aResult);
+};
+
+#endif // nsRDFBinding_h__
diff --git a/dom/xul/templates/nsRDFConInstanceTestNode.cpp b/dom/xul/templates/nsRDFConInstanceTestNode.cpp
new file mode 100644
index 000000000..a96809743
--- /dev/null
+++ b/dom/xul/templates/nsRDFConInstanceTestNode.cpp
@@ -0,0 +1,281 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIComponentManager.h"
+#include "nsIRDFContainer.h"
+#include "nsIRDFContainerUtils.h"
+#include "nsIServiceManager.h"
+#include "nsRDFCID.h"
+#include "nsRDFConInstanceTestNode.h"
+#include "nsResourceSet.h"
+
+#include "mozilla/Logging.h"
+#include "nsXULContentUtils.h"
+
+using mozilla::LogLevel;
+
+extern mozilla::LazyLogModule gXULTemplateLog;
+
+static const char*
+TestToString(nsRDFConInstanceTestNode::Test aTest) {
+ switch (aTest) {
+ case nsRDFConInstanceTestNode::eFalse: return "false";
+ case nsRDFConInstanceTestNode::eTrue: return "true";
+ case nsRDFConInstanceTestNode::eDontCare: return "dontcare";
+ }
+ return "?";
+}
+
+nsRDFConInstanceTestNode::nsRDFConInstanceTestNode(TestNode* aParent,
+ nsXULTemplateQueryProcessorRDF* aProcessor,
+ nsIAtom* aContainerVariable,
+ Test aContainer,
+ Test aEmpty)
+ : nsRDFTestNode(aParent),
+ mProcessor(aProcessor),
+ mContainerVariable(aContainerVariable),
+ mContainer(aContainer),
+ mEmpty(aEmpty)
+{
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ nsAutoCString props;
+
+ nsResourceSet& containmentProps = aProcessor->ContainmentProperties();
+ nsResourceSet::ConstIterator last = containmentProps.Last();
+ nsResourceSet::ConstIterator first = containmentProps.First();
+ nsResourceSet::ConstIterator iter;
+
+ for (iter = first; iter != last; ++iter) {
+ if (iter != first)
+ props += " ";
+
+ const char* str;
+ iter->GetValueConst(&str);
+
+ props += str;
+ }
+
+ nsAutoString cvar(NS_LITERAL_STRING("(none)"));
+ if (mContainerVariable)
+ mContainerVariable->ToString(cvar);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsRDFConInstanceTestNode[%p]: parent=%p member-props=(%s) container-var=%s container=%s empty=%s",
+ this,
+ aParent,
+ props.get(),
+ NS_ConvertUTF16toUTF8(cvar).get(),
+ TestToString(aContainer),
+ TestToString(aEmpty)));
+ }
+}
+
+nsresult
+nsRDFConInstanceTestNode::FilterInstantiations(InstantiationSet& aInstantiations,
+ bool* aCantHandleYet) const
+{
+ nsresult rv;
+
+ if (aCantHandleYet)
+ *aCantHandleYet = false;
+
+ nsCOMPtr<nsIRDFContainerUtils> rdfc
+ = do_GetService("@mozilla.org/rdf/container-utils;1");
+
+ if (! rdfc)
+ return NS_ERROR_FAILURE;
+
+ nsIRDFDataSource* ds = mProcessor->GetDataSource();
+
+ InstantiationSet::Iterator last = aInstantiations.Last();
+ for (InstantiationSet::Iterator inst = aInstantiations.First(); inst != last; ++inst) {
+ nsCOMPtr<nsIRDFNode> value;
+ if (! inst->mAssignments.GetAssignmentFor(mContainerVariable, getter_AddRefs(value))) {
+ NS_ERROR("can't do unbounded container testing");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIRDFResource> valueres = do_QueryInterface(value);
+ if (! valueres) {
+ aInstantiations.Erase(inst--);
+ continue;
+ }
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ const char* container = "(unbound)";
+ valueres->GetValueConst(&container);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsRDFConInstanceTestNode[%p]::FilterInstantiations() container=[%s]",
+ this, container));
+ }
+
+ nsCOMPtr<nsIRDFContainer> rdfcontainer;
+
+ bool isRDFContainer;
+ rv = rdfc->IsContainer(ds, valueres, &isRDFContainer);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mEmpty != eDontCare || mContainer != eDontCare) {
+ Test empty = eDontCare;
+ Test container = eDontCare;
+
+ if (isRDFContainer) {
+ // It's an RDF container. Use the container utilities
+ // to deduce what's in it.
+ container = eTrue;
+
+ // XXX should cache the factory
+ rdfcontainer = do_CreateInstance("@mozilla.org/rdf/container;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = rdfcontainer->Init(ds, valueres);
+ if (NS_FAILED(rv)) return rv;
+
+ int32_t count;
+ rv = rdfcontainer->GetCount(&count);
+ if (NS_FAILED(rv)) return rv;
+
+ empty = (count == 0) ? eTrue : eFalse;
+ } else {
+ empty = eTrue;
+ container = eFalse;
+
+ // First do the simple check of finding some outward
+ // arcs; there should be only a few containment arcs, so this can
+ // save us time from dealing with an iterator later on
+ nsResourceSet& containmentProps = mProcessor->ContainmentProperties();
+ for (nsResourceSet::ConstIterator property = containmentProps.First();
+ property != containmentProps.Last();
+ ++property) {
+ nsCOMPtr<nsIRDFNode> target;
+ rv = ds->GetTarget(valueres, *property, true, getter_AddRefs(target));
+ if (NS_FAILED(rv)) return rv;
+
+ if (target != nullptr) {
+ // bingo. we found one.
+ empty = eFalse;
+ container = eTrue;
+ break;
+ }
+ }
+
+ // if we still don't think its a container, but we
+ // want to know for sure whether it is or not, we need
+ // to check ArcLabelsOut for potential container arcs.
+ if (container == eFalse && mContainer != eDontCare) {
+ nsCOMPtr<nsISimpleEnumerator> arcsout;
+ rv = ds->ArcLabelsOut(valueres, getter_AddRefs(arcsout));
+ if (NS_FAILED(rv)) return rv;
+
+ while (1) {
+ bool hasmore;
+ rv = arcsout->HasMoreElements(&hasmore);
+ if (NS_FAILED(rv)) return rv;
+
+ if (! hasmore)
+ break;
+
+ nsCOMPtr<nsISupports> isupports;
+ rv = arcsout->GetNext(getter_AddRefs(isupports));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIRDFResource> property = do_QueryInterface(isupports);
+ NS_ASSERTION(property != nullptr, "not a property");
+ if (! property)
+ return NS_ERROR_UNEXPECTED;
+
+ if (mProcessor->ContainmentProperties().Contains(property)) {
+ container = eTrue;
+ break;
+ }
+ }
+ }
+ }
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ (" empty => %s",
+ (empty == mEmpty) ? "consistent" : "inconsistent"));
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ (" container => %s",
+ (container == mContainer) ? "consistent" : "inconsistent"));
+
+ if (((mEmpty == empty) && (mContainer == container)) ||
+ ((mEmpty == eDontCare) && (mContainer == container)) ||
+ ((mContainer == eDontCare) && (mEmpty == empty)))
+ {
+ Element* element =
+ new nsRDFConInstanceTestNode::Element(valueres, container, empty);
+ inst->AddSupportingElement(element);
+ }
+ else {
+ aInstantiations.Erase(inst--);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+nsRDFConInstanceTestNode::CanPropagate(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ Instantiation& aInitialBindings) const
+{
+ nsresult rv;
+
+ bool canpropagate = false;
+
+ nsCOMPtr<nsIRDFContainerUtils> rdfc
+ = do_GetService("@mozilla.org/rdf/container-utils;1");
+
+ if (! rdfc)
+ return false;
+
+ // We can certainly propagate ordinal properties
+ rv = rdfc->IsOrdinalProperty(aProperty, &canpropagate);
+ if (NS_FAILED(rv)) return false;
+
+ if (! canpropagate) {
+ canpropagate = mProcessor->ContainmentProperties().Contains(aProperty);
+ }
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ const char* source;
+ aSource->GetValueConst(&source);
+
+ const char* property;
+ aProperty->GetValueConst(&property);
+
+ nsAutoString target;
+ nsXULContentUtils::GetTextForNode(aTarget, target);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsRDFConInstanceTestNode[%p]: CanPropagate([%s]==[%s]=>[%s]) => %s",
+ this, source, property, NS_ConvertUTF16toUTF8(target).get(),
+ canpropagate ? "true" : "false"));
+ }
+
+ if (canpropagate) {
+ aInitialBindings.AddAssignment(mContainerVariable, aSource);
+ return true;
+ }
+
+ return false;
+}
+
+void
+nsRDFConInstanceTestNode::Retract(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget) const
+{
+ // XXXwaterson oof. complicated. figure this out.
+ if (0) {
+ mProcessor->RetractElement(Element(aSource, mContainer, mEmpty));
+ }
+}
+
diff --git a/dom/xul/templates/nsRDFConInstanceTestNode.h b/dom/xul/templates/nsRDFConInstanceTestNode.h
new file mode 100644
index 000000000..0ed96909e
--- /dev/null
+++ b/dom/xul/templates/nsRDFConInstanceTestNode.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsRDFConInstanceTestNode_h__
+#define nsRDFConInstanceTestNode_h__
+
+#include "mozilla/Attributes.h"
+#include "nscore.h"
+#include "nsRDFTestNode.h"
+#include "nsIRDFResource.h"
+#include "nsIRDFDataSource.h"
+#include "nsXULTemplateQueryProcessorRDF.h"
+
+/**
+ * Rule network node that tests if a resource is an RDF container, or
+ * uses multi-attributes to ``contain'' other elements.
+ */
+class nsRDFConInstanceTestNode : public nsRDFTestNode
+{
+public:
+ enum Test { eFalse, eTrue, eDontCare };
+
+ nsRDFConInstanceTestNode(TestNode* aParent,
+ nsXULTemplateQueryProcessorRDF* aProcessor,
+ nsIAtom* aContainerVariable,
+ Test aContainer,
+ Test aEmpty);
+
+ virtual nsresult FilterInstantiations(InstantiationSet& aInstantiations,
+ bool* aCantHandleYet) const override;
+
+ virtual bool
+ CanPropagate(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ Instantiation& aInitialBindings) const override;
+
+ virtual void
+ Retract(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget) const override;
+
+
+ class Element : public MemoryElement {
+ public:
+ Element(nsIRDFResource* aContainer,
+ Test aContainerTest,
+ Test aEmptyTest)
+ : mContainer(aContainer),
+ mContainerTest(aContainerTest),
+ mEmptyTest(aEmptyTest) {
+ MOZ_COUNT_CTOR(nsRDFConInstanceTestNode::Element); }
+
+ virtual ~Element() { MOZ_COUNT_DTOR(nsRDFConInstanceTestNode::Element); }
+
+ virtual const char* Type() const override {
+ return "nsRDFConInstanceTestNode::Element"; }
+
+ virtual PLHashNumber Hash() const override {
+ return mozilla::HashGeneric(mContainerTest, mEmptyTest, mContainer.get());
+ }
+
+ virtual bool Equals(const MemoryElement& aElement) const override {
+ if (aElement.Type() == Type()) {
+ const Element& element = static_cast<const Element&>(aElement);
+ return mContainer == element.mContainer
+ && mContainerTest == element.mContainerTest
+ && mEmptyTest == element.mEmptyTest;
+ }
+ return false; }
+
+ protected:
+ nsCOMPtr<nsIRDFResource> mContainer;
+ Test mContainerTest;
+ Test mEmptyTest;
+ };
+
+protected:
+ nsXULTemplateQueryProcessorRDF* mProcessor;
+ nsCOMPtr<nsIAtom> mContainerVariable;
+ Test mContainer;
+ Test mEmpty;
+};
+
+#endif // nsRDFConInstanceTestNode_h__
+
diff --git a/dom/xul/templates/nsRDFConMemberTestNode.cpp b/dom/xul/templates/nsRDFConMemberTestNode.cpp
new file mode 100644
index 000000000..0bb96a5b5
--- /dev/null
+++ b/dom/xul/templates/nsRDFConMemberTestNode.cpp
@@ -0,0 +1,510 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsRDFConMemberTestNode.h"
+#include "nsIRDFContainer.h"
+#include "nsIRDFContainerUtils.h"
+#include "nsRDFCID.h"
+#include "nsIServiceManager.h"
+#include "nsResourceSet.h"
+#include "nsString.h"
+#include "nsXULContentUtils.h"
+
+#include "mozilla/Logging.h"
+
+using mozilla::LogLevel;
+
+extern mozilla::LazyLogModule gXULTemplateLog;
+
+nsRDFConMemberTestNode::nsRDFConMemberTestNode(TestNode* aParent,
+ nsXULTemplateQueryProcessorRDF* aProcessor,
+ nsIAtom *aContainerVariable,
+ nsIAtom *aMemberVariable)
+ : nsRDFTestNode(aParent),
+ mProcessor(aProcessor),
+ mContainerVariable(aContainerVariable),
+ mMemberVariable(aMemberVariable)
+{
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ nsAutoCString props;
+
+ nsResourceSet& containmentProps = aProcessor->ContainmentProperties();
+ nsResourceSet::ConstIterator last = containmentProps.Last();
+ nsResourceSet::ConstIterator first = containmentProps.First();
+ nsResourceSet::ConstIterator iter;
+
+ for (iter = first; iter != last; ++iter) {
+ if (iter != first)
+ props += " ";
+
+ const char* str;
+ iter->GetValueConst(&str);
+
+ props += str;
+ }
+
+ nsAutoString cvar(NS_LITERAL_STRING("(none)"));
+ if (mContainerVariable)
+ mContainerVariable->ToString(cvar);
+
+ nsAutoString mvar(NS_LITERAL_STRING("(none)"));
+ if (mMemberVariable)
+ mMemberVariable->ToString(mvar);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsRDFConMemberTestNode[%p]: parent=%p member-props=(%s) container-var=%s member-var=%s",
+ this,
+ aParent,
+ props.get(),
+ NS_ConvertUTF16toUTF8(cvar).get(),
+ NS_ConvertUTF16toUTF8(mvar).get()));
+ }
+}
+
+nsresult
+nsRDFConMemberTestNode::FilterInstantiations(InstantiationSet& aInstantiations,
+ bool* aCantHandleYet) const
+{
+ // XXX Uh, factor me, please!
+ nsresult rv;
+
+ if (aCantHandleYet)
+ *aCantHandleYet = false;
+
+ nsCOMPtr<nsIRDFContainerUtils> rdfc =
+ do_GetService("@mozilla.org/rdf/container-utils;1");
+
+ if (! rdfc)
+ return NS_ERROR_FAILURE;
+
+ nsIRDFDataSource* ds = mProcessor->GetDataSource();
+
+ InstantiationSet::Iterator last = aInstantiations.Last();
+ for (InstantiationSet::Iterator inst = aInstantiations.First(); inst != last; ++inst) {
+ bool hasContainerBinding;
+ nsCOMPtr<nsIRDFNode> containerValue;
+ hasContainerBinding = inst->mAssignments.GetAssignmentFor(mContainerVariable,
+ getter_AddRefs(containerValue));
+
+ nsCOMPtr<nsIRDFResource> containerRes = do_QueryInterface(containerValue);
+
+ nsCOMPtr<nsIRDFContainer> rdfcontainer;
+
+ if (hasContainerBinding && containerRes) {
+ // If we have a container assignment, then see if the
+ // container is an RDF container (bag, seq, alt), and if
+ // so, wrap it.
+ bool isRDFContainer;
+ rv = rdfc->IsContainer(ds, containerRes, &isRDFContainer);
+ if (NS_FAILED(rv)) return rv;
+
+ if (isRDFContainer) {
+ rdfcontainer = do_CreateInstance("@mozilla.org/rdf/container;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = rdfcontainer->Init(ds, containerRes);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+
+ bool hasMemberBinding;
+ nsCOMPtr<nsIRDFNode> memberValue;
+ hasMemberBinding = inst->mAssignments.GetAssignmentFor(mMemberVariable,
+ getter_AddRefs(memberValue));
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ const char* container = "(unbound)";
+ if (hasContainerBinding)
+ containerRes->GetValueConst(&container);
+
+ nsAutoString member(NS_LITERAL_STRING("(unbound)"));
+ if (hasMemberBinding)
+ nsXULContentUtils::GetTextForNode(memberValue, member);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsRDFConMemberTestNode[%p]: FilterInstantiations() container=[%s] member=[%s]",
+ this, container, NS_ConvertUTF16toUTF8(member).get()));
+ }
+
+ if (hasContainerBinding && hasMemberBinding) {
+ // it's a consistency check. see if we have a assignment that is consistent
+ bool isconsistent = false;
+
+ if (rdfcontainer) {
+ // RDF containers are easy. Just use the container API.
+ int32_t index;
+ rv = rdfcontainer->IndexOf(memberValue, &index);
+ if (NS_FAILED(rv)) return rv;
+
+ if (index >= 0)
+ isconsistent = true;
+ }
+
+ // XXXwaterson oof. if we *are* an RDF container, why do
+ // we still need to grovel through all the containment
+ // properties if the thing we're looking for wasn't there?
+
+ if (! isconsistent) {
+ // Othewise, we'll need to grovel through the
+ // membership properties to see if we have an
+ // assertion that indicates membership.
+ nsResourceSet& containmentProps = mProcessor->ContainmentProperties();
+ for (nsResourceSet::ConstIterator property = containmentProps.First();
+ property != containmentProps.Last();
+ ++property) {
+ bool hasAssertion;
+ rv = ds->HasAssertion(containerRes,
+ *property,
+ memberValue,
+ true,
+ &hasAssertion);
+ if (NS_FAILED(rv)) return rv;
+
+ if (hasAssertion) {
+ // it's consistent. leave it in the set and we'll
+ // run it up to our parent.
+ isconsistent = true;
+ break;
+ }
+ }
+ }
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ (" consistency check => %s", isconsistent ? "passed" : "failed"));
+
+ if (isconsistent) {
+ // Add a memory element to our set-of-support.
+ Element* element =
+ new nsRDFConMemberTestNode::Element(containerRes,
+ memberValue);
+ inst->AddSupportingElement(element);
+ }
+ else {
+ // it's inconsistent. remove it.
+ aInstantiations.Erase(inst--);
+ }
+
+ // We're done, go on to the next instantiation
+ continue;
+ }
+
+ if (hasContainerBinding && rdfcontainer) {
+ // We've got a container assignment, and the container is
+ // bound to an RDF container. Add each member as a new
+ // instantiation.
+ nsCOMPtr<nsISimpleEnumerator> elements;
+ rv = rdfcontainer->GetElements(getter_AddRefs(elements));
+ if (NS_FAILED(rv)) return rv;
+
+ while (1) {
+ bool hasmore;
+ rv = elements->HasMoreElements(&hasmore);
+ if (NS_FAILED(rv)) return rv;
+
+ if (! hasmore)
+ break;
+
+ nsCOMPtr<nsISupports> isupports;
+ rv = elements->GetNext(getter_AddRefs(isupports));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIRDFNode> node = do_QueryInterface(isupports);
+ if (! node)
+ return NS_ERROR_UNEXPECTED;
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ nsAutoString member;
+ nsXULContentUtils::GetTextForNode(node, member);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ (" member => %s", NS_ConvertUTF16toUTF8(member).get()));
+ }
+
+ Instantiation newinst = *inst;
+ newinst.AddAssignment(mMemberVariable, node);
+
+ Element* element =
+ new nsRDFConMemberTestNode::Element(containerRes, node);
+ newinst.AddSupportingElement(element);
+ aInstantiations.Insert(inst, newinst);
+ }
+ }
+
+ if (hasMemberBinding) {
+ // Oh, this is so nasty. If we have a member assignment, then
+ // grovel through each one of our inbound arcs to see if
+ // any of them are ordinal properties (like an RDF
+ // container might have). If so, walk it backwards to get
+ // the container we're in.
+ nsCOMPtr<nsISimpleEnumerator> arcsin;
+ rv = ds->ArcLabelsIn(memberValue, getter_AddRefs(arcsin));
+ if (NS_FAILED(rv)) return rv;
+
+ while (1) {
+ nsCOMPtr<nsIRDFResource> property;
+
+ {
+ bool hasmore;
+ rv = arcsin->HasMoreElements(&hasmore);
+ if (NS_FAILED(rv)) return rv;
+
+ if (! hasmore)
+ break;
+
+ nsCOMPtr<nsISupports> isupports;
+ rv = arcsin->GetNext(getter_AddRefs(isupports));
+ if (NS_FAILED(rv)) return rv;
+
+ property = do_QueryInterface(isupports);
+ if (! property)
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Ordinal properties automagically indicate container
+ // membership as far as we're concerned. Note that
+ // we're *only* concerned with ordinal properties
+ // here: the next block will worry about the other
+ // membership properties.
+ bool isordinal;
+ rv = rdfc->IsOrdinalProperty(property, &isordinal);
+ if (NS_FAILED(rv)) return rv;
+
+ if (isordinal) {
+ // If we get here, we've found a property that
+ // indicates container membership leading *into* a
+ // member node. Find all the people that point to
+ // it, and call them containers.
+ nsCOMPtr<nsISimpleEnumerator> sources;
+ rv = ds->GetSources(property, memberValue, true,
+ getter_AddRefs(sources));
+ if (NS_FAILED(rv)) return rv;
+
+ while (1) {
+ bool hasmore;
+ rv = sources->HasMoreElements(&hasmore);
+ if (NS_FAILED(rv)) return rv;
+
+ if (! hasmore)
+ break;
+
+ nsCOMPtr<nsISupports> isupports;
+ rv = sources->GetNext(getter_AddRefs(isupports));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIRDFResource> source = do_QueryInterface(isupports);
+ if (! source)
+ return NS_ERROR_UNEXPECTED;
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ const char* container;
+ source->GetValueConst(&container);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ (" container => %s", container));
+ }
+
+ // Add a new instantiation
+ Instantiation newinst = *inst;
+ newinst.AddAssignment(mContainerVariable, source);
+
+ Element* element =
+ new nsRDFConMemberTestNode::Element(source,
+ memberValue);
+ newinst.AddSupportingElement(element);
+
+ aInstantiations.Insert(inst, newinst);
+ }
+ }
+ }
+ }
+
+ if ((hasContainerBinding && ! hasMemberBinding) ||
+ (! hasContainerBinding && hasMemberBinding)) {
+ // it's an open ended query on the container or member. go
+ // through our containment properties to see if anything
+ // applies.
+ nsResourceSet& containmentProps = mProcessor->ContainmentProperties();
+ for (nsResourceSet::ConstIterator property = containmentProps.First();
+ property != containmentProps.Last();
+ ++property) {
+ nsCOMPtr<nsISimpleEnumerator> results;
+ if (hasContainerBinding) {
+ rv = ds->GetTargets(containerRes, *property, true,
+ getter_AddRefs(results));
+ }
+ else {
+ rv = ds->GetSources(*property, memberValue, true,
+ getter_AddRefs(results));
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ while (1) {
+ bool hasmore;
+ rv = results->HasMoreElements(&hasmore);
+ if (NS_FAILED(rv)) return rv;
+
+ if (! hasmore)
+ break;
+
+ nsCOMPtr<nsISupports> isupports;
+ rv = results->GetNext(getter_AddRefs(isupports));
+ if (NS_FAILED(rv)) return rv;
+
+ nsIAtom* variable;
+ nsCOMPtr<nsIRDFNode> value;
+ nsCOMPtr<nsIRDFResource> valueRes;
+
+ if (hasContainerBinding) {
+ variable = mMemberVariable;
+
+ value = do_QueryInterface(isupports);
+ NS_ASSERTION(value != nullptr, "member is not an nsIRDFNode");
+ if (! value) continue;
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ nsAutoString s;
+ nsXULContentUtils::GetTextForNode(value, s);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ (" member => %s", NS_ConvertUTF16toUTF8(s).get()));
+ }
+ }
+ else {
+ variable = mContainerVariable;
+
+ valueRes = do_QueryInterface(isupports);
+ NS_ASSERTION(valueRes != nullptr, "container is not an nsIRDFResource");
+ if (! valueRes) continue;
+
+ value = valueRes;
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ const char* s;
+ valueRes->GetValueConst(&s);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ (" container => %s", s));
+ }
+ }
+
+ // Copy the original instantiation, and add it to the
+ // instantiation set with the new assignment that we've
+ // introduced. Ownership will be transferred to the
+ Instantiation newinst = *inst;
+ newinst.AddAssignment(variable, value);
+
+ Element* element;
+ if (hasContainerBinding) {
+ element =
+ new nsRDFConMemberTestNode::Element(containerRes, value);
+ }
+ else {
+ element =
+ new nsRDFConMemberTestNode::Element(valueRes, memberValue);
+ }
+
+ if (! element)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ newinst.AddSupportingElement(element);
+
+ 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 = true;
+ return NS_OK;
+ }
+
+ // finally, remove the "under specified" instantiation.
+ aInstantiations.Erase(inst--);
+ }
+
+ return NS_OK;
+}
+
+bool
+nsRDFConMemberTestNode::CanPropagate(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ Instantiation& aInitialBindings) const
+{
+ nsresult rv;
+
+ bool canpropagate = false;
+
+ nsCOMPtr<nsIRDFContainerUtils> rdfc =
+ do_GetService("@mozilla.org/rdf/container-utils;1");
+
+ if (! rdfc)
+ return false;
+
+ // We can certainly propagate ordinal properties
+ rv = rdfc->IsOrdinalProperty(aProperty, &canpropagate);
+ if (NS_FAILED(rv)) return false;
+
+ if (! canpropagate) {
+ canpropagate = mProcessor->ContainmentProperties().Contains(aProperty);
+ }
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ const char* source;
+ aSource->GetValueConst(&source);
+
+ const char* property;
+ aProperty->GetValueConst(&property);
+
+ nsAutoString target;
+ nsXULContentUtils::GetTextForNode(aTarget, target);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsRDFConMemberTestNode[%p]: CanPropagate([%s]==[%s]=>[%s]) => %s",
+ this, source, property, NS_ConvertUTF16toUTF8(target).get(),
+ canpropagate ? "true" : "false"));
+ }
+
+ if (canpropagate) {
+ aInitialBindings.AddAssignment(mContainerVariable, aSource);
+ aInitialBindings.AddAssignment(mMemberVariable, aTarget);
+ return true;
+ }
+
+ return false;
+}
+
+void
+nsRDFConMemberTestNode::Retract(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget) const
+{
+ bool canretract = false;
+
+ nsCOMPtr<nsIRDFContainerUtils> rdfc =
+ do_GetService("@mozilla.org/rdf/container-utils;1");
+
+ if (! rdfc)
+ return;
+
+ // We can certainly retract ordinal properties
+ nsresult rv;
+ rv = rdfc->IsOrdinalProperty(aProperty, &canretract);
+ if (NS_FAILED(rv)) return;
+
+ if (! canretract) {
+ canretract = mProcessor->ContainmentProperties().Contains(aProperty);
+ }
+
+ if (canretract) {
+ mProcessor->RetractElement(Element(aSource, aTarget));
+ }
+}
diff --git a/dom/xul/templates/nsRDFConMemberTestNode.h b/dom/xul/templates/nsRDFConMemberTestNode.h
new file mode 100644
index 000000000..4db2f8983
--- /dev/null
+++ b/dom/xul/templates/nsRDFConMemberTestNode.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsRDFConMemberTestNode_h__
+#define nsRDFConMemberTestNode_h__
+
+#include "mozilla/Attributes.h"
+#include "nscore.h"
+#include "nsRDFTestNode.h"
+#include "nsIRDFDataSource.h"
+#include "nsXULTemplateQueryProcessorRDF.h"
+
+/**
+ * Rule network node that test if a resource is a member of an RDF
+ * container, or is ``contained'' by another resource that refers to
+ * it using a ``containment'' attribute.
+ */
+class nsRDFConMemberTestNode : public nsRDFTestNode
+{
+public:
+ nsRDFConMemberTestNode(TestNode* aParent,
+ nsXULTemplateQueryProcessorRDF* aProcessor,
+ nsIAtom* aContainerVariable,
+ nsIAtom* aMemberVariable);
+
+ virtual nsresult FilterInstantiations(InstantiationSet& aInstantiations,
+ bool* aCantHandleYet) const override;
+
+ virtual bool
+ CanPropagate(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ Instantiation& aInitialBindings) const override;
+
+ virtual void
+ Retract(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget) const override;
+
+ class Element : public MemoryElement {
+ public:
+ Element(nsIRDFResource* aContainer,
+ nsIRDFNode* aMember)
+ : mContainer(aContainer),
+ mMember(aMember) {
+ MOZ_COUNT_CTOR(nsRDFConMemberTestNode::Element); }
+
+ virtual ~Element() { MOZ_COUNT_DTOR(nsRDFConMemberTestNode::Element); }
+
+ virtual const char* Type() const override {
+ return "nsRDFConMemberTestNode::Element"; }
+
+ virtual PLHashNumber Hash() const override {
+ return PLHashNumber(NS_PTR_TO_INT32(mContainer.get())) ^
+ (PLHashNumber(NS_PTR_TO_INT32(mMember.get())) >> 12); }
+
+ virtual bool Equals(const MemoryElement& aElement) const override {
+ if (aElement.Type() == Type()) {
+ const Element& element = static_cast<const Element&>(aElement);
+ return mContainer == element.mContainer && mMember == element.mMember;
+ }
+ return false; }
+
+ protected:
+ nsCOMPtr<nsIRDFResource> mContainer;
+ nsCOMPtr<nsIRDFNode> mMember;
+ };
+
+protected:
+ nsXULTemplateQueryProcessorRDF* mProcessor;
+ nsCOMPtr<nsIAtom> mContainerVariable;
+ nsCOMPtr<nsIAtom> mMemberVariable;
+};
+
+#endif // nsRDFConMemberTestNode_h__
diff --git a/dom/xul/templates/nsRDFPropertyTestNode.cpp b/dom/xul/templates/nsRDFPropertyTestNode.cpp
new file mode 100644
index 000000000..2fa08f2b8
--- /dev/null
+++ b/dom/xul/templates/nsRDFPropertyTestNode.cpp
@@ -0,0 +1,362 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsRDFPropertyTestNode.h"
+#include "nsString.h"
+#include "nsXULContentUtils.h"
+
+#include "mozilla/Logging.h"
+
+using mozilla::LogLevel;
+
+extern mozilla::LazyLogModule gXULTemplateLog;
+#include "nsIRDFLiteral.h"
+
+nsRDFPropertyTestNode::nsRDFPropertyTestNode(TestNode* aParent,
+ nsXULTemplateQueryProcessorRDF* aProcessor,
+ nsIAtom* aSourceVariable,
+ nsIRDFResource* aProperty,
+ nsIAtom* aTargetVariable)
+ : nsRDFTestNode(aParent),
+ mProcessor(aProcessor),
+ mSourceVariable(aSourceVariable),
+ mSource(nullptr),
+ mProperty(aProperty),
+ mTargetVariable(aTargetVariable),
+ mTarget(nullptr)
+{
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ const char* prop = "(null)";
+ if (aProperty)
+ aProperty->GetValueConst(&prop);
+
+ nsAutoString svar(NS_LITERAL_STRING("(none)"));
+ if (mSourceVariable)
+ mSourceVariable->ToString(svar);
+
+ nsAutoString tvar(NS_LITERAL_STRING("(none)"));
+ if (mTargetVariable)
+ mTargetVariable->ToString(tvar);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsRDFPropertyTestNode[%p]: parent=%p source=%s property=%s target=%s",
+ this, aParent, NS_ConvertUTF16toUTF8(svar).get(), prop, NS_ConvertUTF16toUTF8(tvar).get()));
+ }
+}
+
+
+nsRDFPropertyTestNode::nsRDFPropertyTestNode(TestNode* aParent,
+ nsXULTemplateQueryProcessorRDF* aProcessor,
+ nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIAtom* aTargetVariable)
+ : nsRDFTestNode(aParent),
+ mProcessor(aProcessor),
+ mSourceVariable(nullptr),
+ mSource(aSource),
+ mProperty(aProperty),
+ mTargetVariable(aTargetVariable),
+ mTarget(nullptr)
+{
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ const char* source = "(null)";
+ if (aSource)
+ aSource->GetValueConst(&source);
+
+ const char* prop = "(null)";
+ if (aProperty)
+ aProperty->GetValueConst(&prop);
+
+ nsAutoString tvar(NS_LITERAL_STRING("(none)"));
+ if (mTargetVariable)
+ mTargetVariable->ToString(tvar);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsRDFPropertyTestNode[%p]: parent=%p source=%s property=%s target=%s",
+ this, aParent, source, prop, NS_ConvertUTF16toUTF8(tvar).get()));
+ }
+}
+
+
+nsRDFPropertyTestNode::nsRDFPropertyTestNode(TestNode* aParent,
+ nsXULTemplateQueryProcessorRDF* aProcessor,
+ nsIAtom* aSourceVariable,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+ : nsRDFTestNode(aParent),
+ mProcessor(aProcessor),
+ mSourceVariable(aSourceVariable),
+ mSource(nullptr),
+ mProperty(aProperty),
+ mTargetVariable(nullptr),
+ mTarget(aTarget)
+{
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ nsAutoString svar(NS_LITERAL_STRING("(none)"));
+ if (mSourceVariable)
+ mSourceVariable->ToString(svar);
+
+ const char* prop = "(null)";
+ if (aProperty)
+ aProperty->GetValueConst(&prop);
+
+ nsAutoString target;
+ nsXULContentUtils::GetTextForNode(aTarget, target);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsRDFPropertyTestNode[%p]: parent=%p source=%s property=%s target=%s",
+ this, aParent, NS_ConvertUTF16toUTF8(svar).get(), prop, NS_ConvertUTF16toUTF8(target).get()));
+ }
+}
+
+
+nsresult
+nsRDFPropertyTestNode::FilterInstantiations(InstantiationSet& aInstantiations,
+ bool* aCantHandleYet) const
+{
+ nsresult rv;
+
+ if (aCantHandleYet)
+ *aCantHandleYet = false;
+
+ nsIRDFDataSource* ds = mProcessor->GetDataSource();
+
+ InstantiationSet::Iterator last = aInstantiations.Last();
+ for (InstantiationSet::Iterator inst = aInstantiations.First(); inst != last; ++inst) {
+ bool hasSourceBinding;
+ nsCOMPtr<nsIRDFResource> sourceRes;
+
+ if (mSource) {
+ hasSourceBinding = true;
+ sourceRes = mSource;
+ }
+ else {
+ nsCOMPtr<nsIRDFNode> sourceValue;
+ hasSourceBinding = inst->mAssignments.GetAssignmentFor(mSourceVariable,
+ getter_AddRefs(sourceValue));
+ sourceRes = do_QueryInterface(sourceValue);
+ }
+
+ bool hasTargetBinding;
+ nsCOMPtr<nsIRDFNode> targetValue;
+
+ if (mTarget) {
+ hasTargetBinding = true;
+ targetValue = mTarget;
+ }
+ else {
+ hasTargetBinding = inst->mAssignments.GetAssignmentFor(mTargetVariable,
+ getter_AddRefs(targetValue));
+ }
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ const char* source = "(unbound)";
+ if (hasSourceBinding)
+ sourceRes->GetValueConst(&source);
+
+ nsAutoString target(NS_LITERAL_STRING("(unbound)"));
+ if (hasTargetBinding)
+ nsXULContentUtils::GetTextForNode(targetValue, target);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsRDFPropertyTestNode[%p]: FilterInstantiations() source=[%s] target=[%s]",
+ this, source, NS_ConvertUTF16toUTF8(target).get()));
+ }
+
+ if (hasSourceBinding && hasTargetBinding) {
+ // it's a consistency check. see if we have a assignment that is consistent
+ bool hasAssertion;
+ rv = ds->HasAssertion(sourceRes, mProperty, targetValue,
+ true, &hasAssertion);
+ if (NS_FAILED(rv)) return rv;
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ (" consistency check => %s", hasAssertion ? "passed" : "failed"));
+
+ if (hasAssertion) {
+ // it's consistent.
+ Element* element =
+ new nsRDFPropertyTestNode::Element(sourceRes, mProperty,
+ targetValue);
+ inst->AddSupportingElement(element);
+ }
+ else {
+ // it's inconsistent. remove it.
+ aInstantiations.Erase(inst--);
+ }
+ }
+ else if ((hasSourceBinding && ! hasTargetBinding) ||
+ (! hasSourceBinding && hasTargetBinding)) {
+ // it's an open ended query on the source or
+ // target. figure out what matches and add as a
+ // cross-product.
+ nsCOMPtr<nsISimpleEnumerator> results;
+ if (hasSourceBinding) {
+ rv = ds->GetTargets(sourceRes,
+ mProperty,
+ true,
+ getter_AddRefs(results));
+ }
+ else {
+ rv = ds->GetSources(mProperty,
+ targetValue,
+ true,
+ getter_AddRefs(results));
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ while (1) {
+ bool hasMore;
+ rv = results->HasMoreElements(&hasMore);
+ if (NS_FAILED(rv)) return rv;
+
+ if (! hasMore)
+ break;
+
+ nsCOMPtr<nsISupports> isupports;
+ rv = results->GetNext(getter_AddRefs(isupports));
+ if (NS_FAILED(rv)) return rv;
+
+ nsIAtom* variable;
+ nsCOMPtr<nsIRDFNode> value;
+
+ if (hasSourceBinding) {
+ variable = mTargetVariable;
+
+ value = do_QueryInterface(isupports);
+ NS_ASSERTION(value != nullptr, "target is not an nsIRDFNode");
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ nsAutoString s(NS_LITERAL_STRING("(none found)"));
+ if (value)
+ nsXULContentUtils::GetTextForNode(value, s);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ (" target => %s", NS_ConvertUTF16toUTF8(s).get()));
+ }
+
+ if (! value) continue;
+
+ targetValue = value;
+ }
+ else {
+ variable = mSourceVariable;
+
+ nsCOMPtr<nsIRDFResource> source = do_QueryInterface(isupports);
+ NS_ASSERTION(source != nullptr, "source is not an nsIRDFResource");
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ const char* s = "(none found)";
+ if (source)
+ source->GetValueConst(&s);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ (" source => %s", s));
+ }
+
+ if (! source) continue;
+
+ value = sourceRes = source;
+ }
+
+ // Copy the original instantiation, and add it to the
+ // instantiation set with the new assignment that we've
+ // introduced. Ownership will be transferred to the
+ Instantiation newinst = *inst;
+ newinst.AddAssignment(variable, value);
+
+ Element* element =
+ new nsRDFPropertyTestNode::Element(sourceRes, mProperty,
+ targetValue);
+ newinst.AddSupportingElement(element);
+
+ 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 = true;
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+nsRDFPropertyTestNode::CanPropagate(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ Instantiation& aInitialBindings) const
+{
+ bool result;
+
+ if ((mProperty.get() != aProperty) ||
+ (mSource && mSource.get() != aSource) ||
+ (mTarget && mTarget.get() != aTarget)) {
+ result = false;
+ }
+ else {
+ if (mSourceVariable)
+ aInitialBindings.AddAssignment(mSourceVariable, aSource);
+
+ if (mTargetVariable)
+ aInitialBindings.AddAssignment(mTargetVariable, aTarget);
+
+ result = true;
+ }
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ const char* source;
+ aSource->GetValueConst(&source);
+
+ const char* property;
+ aProperty->GetValueConst(&property);
+
+ nsAutoString target;
+ nsXULContentUtils::GetTextForNode(aTarget, target);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsRDFPropertyTestNode[%p]: CanPropagate([%s]==[%s]=>[%s]) => %s",
+ this, source, property, NS_ConvertUTF16toUTF8(target).get(),
+ result ? "true" : "false"));
+ }
+
+ return result;
+}
+
+void
+nsRDFPropertyTestNode::Retract(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget) const
+{
+ if (aProperty == mProperty.get()) {
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ const char* source;
+ aSource->GetValueConst(&source);
+
+ const char* property;
+ aProperty->GetValueConst(&property);
+
+ nsAutoString target;
+ nsXULContentUtils::GetTextForNode(aTarget, target);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsRDFPropertyTestNode[%p]: Retract([%s]==[%s]=>[%s])",
+ this, source, property, NS_ConvertUTF16toUTF8(target).get()));
+ }
+
+ mProcessor->RetractElement(Element(aSource, aProperty, aTarget));
+ }
+}
+
diff --git a/dom/xul/templates/nsRDFPropertyTestNode.h b/dom/xul/templates/nsRDFPropertyTestNode.h
new file mode 100644
index 000000000..673552a26
--- /dev/null
+++ b/dom/xul/templates/nsRDFPropertyTestNode.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsRDFPropertyTestNode_h__
+#define nsRDFPropertyTestNode_h__
+
+#include "mozilla/Attributes.h"
+#include "nscore.h"
+#include "nsRDFTestNode.h"
+#include "nsIRDFDataSource.h"
+#include "nsIRDFResource.h"
+#include "nsXULTemplateQueryProcessorRDF.h"
+
+class nsRDFPropertyTestNode : public nsRDFTestNode
+{
+public:
+ /**
+ * Both source and target unbound (?source ^property ?target)
+ */
+ nsRDFPropertyTestNode(TestNode* aParent,
+ nsXULTemplateQueryProcessorRDF* aProcessor,
+ nsIAtom* aSourceVariable,
+ nsIRDFResource* aProperty,
+ nsIAtom* aTargetVariable);
+
+ /**
+ * Source bound, target unbound (source ^property ?target)
+ */
+ nsRDFPropertyTestNode(TestNode* aParent,
+ nsXULTemplateQueryProcessorRDF* aProcessor,
+ nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIAtom* aTargetVariable);
+
+ /**
+ * Source unbound, target bound (?source ^property target)
+ */
+ nsRDFPropertyTestNode(TestNode* aParent,
+ nsXULTemplateQueryProcessorRDF* aProcessor,
+ nsIAtom* aSourceVariable,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget);
+
+ virtual nsresult FilterInstantiations(InstantiationSet& aInstantiations,
+ bool* aCantHandleYet) const override;
+
+ virtual bool
+ CanPropagate(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ Instantiation& aInitialBindings) const override;
+
+ virtual void
+ Retract(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget) const override;
+
+
+ class Element : public MemoryElement {
+ public:
+ Element(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+ : mSource(aSource),
+ mProperty(aProperty),
+ mTarget(aTarget) {
+ MOZ_COUNT_CTOR(nsRDFPropertyTestNode::Element); }
+
+ virtual ~Element() { MOZ_COUNT_DTOR(nsRDFPropertyTestNode::Element); }
+
+ virtual const char* Type() const override {
+ return "nsRDFPropertyTestNode::Element"; }
+
+ virtual PLHashNumber Hash() const override {
+ return mozilla::HashGeneric(mSource.get(), mProperty.get(), mTarget.get());
+ }
+
+ virtual bool Equals(const MemoryElement& aElement) const override {
+ if (aElement.Type() == Type()) {
+ const Element& element = static_cast<const Element&>(aElement);
+ return mSource == element.mSource
+ && mProperty == element.mProperty
+ && mTarget == element.mTarget;
+ }
+ return false; }
+
+ protected:
+ nsCOMPtr<nsIRDFResource> mSource;
+ nsCOMPtr<nsIRDFResource> mProperty;
+ nsCOMPtr<nsIRDFNode> mTarget;
+ };
+
+protected:
+ nsXULTemplateQueryProcessorRDF* mProcessor;
+ nsCOMPtr<nsIAtom> mSourceVariable;
+ nsCOMPtr<nsIRDFResource> mSource;
+ nsCOMPtr<nsIRDFResource> mProperty;
+ nsCOMPtr<nsIAtom> mTargetVariable;
+ nsCOMPtr<nsIRDFNode> mTarget;
+};
+
+#endif // nsRDFPropertyTestNode_h__
diff --git a/dom/xul/templates/nsRDFQuery.cpp b/dom/xul/templates/nsRDFQuery.cpp
new file mode 100644
index 000000000..b4eb706ed
--- /dev/null
+++ b/dom/xul/templates/nsRDFQuery.cpp
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nscore.h"
+#include "nsCOMPtr.h"
+
+#include "nsXULTemplateQueryProcessorRDF.h"
+#include "nsRDFQuery.h"
+
+NS_IMPL_CYCLE_COLLECTION(nsRDFQuery, mQueryNode)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsRDFQuery)
+ NS_INTERFACE_MAP_ENTRY(nsITemplateRDFQuery)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsRDFQuery)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsRDFQuery)
+
+void
+nsRDFQuery::Finish()
+{
+ // the template builder is going away and the query processor likely as
+ // well. Clear the reference to avoid calling it.
+ mProcessor = nullptr;
+ mCachedResults = nullptr;
+}
+
+nsresult
+nsRDFQuery::SetCachedResults(nsXULTemplateQueryProcessorRDF* aProcessor,
+ const InstantiationSet& aInstantiations)
+{
+ mCachedResults = new nsXULTemplateResultSetRDF(aProcessor, this, &aInstantiations);
+ return NS_OK;
+}
+
+
+void
+nsRDFQuery::UseCachedResults(nsISimpleEnumerator** aResults)
+{
+ *aResults = mCachedResults;
+ NS_IF_ADDREF(*aResults);
+
+ mCachedResults = nullptr;
+}
diff --git a/dom/xul/templates/nsRDFQuery.h b/dom/xul/templates/nsRDFQuery.h
new file mode 100644
index 000000000..572cce4d3
--- /dev/null
+++ b/dom/xul/templates/nsRDFQuery.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsRDFQuery_h__
+#define nsRDFQuery_h__
+
+#include "nsISimpleEnumerator.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+
+#define NS_ITEMPLATERDFQUERY_IID \
+ {0x8929ff60, 0x1c9c, 0x4d87, \
+ { 0xac, 0x02, 0x09, 0x14, 0x15, 0x3b, 0x48, 0xc4 }}
+
+class nsXULTemplateQueryProcessorRDF;
+
+/**
+ * A compiled query in the RDF query processor. This interface should not be
+ * used directly outside of the RDF query processor.
+ */
+class nsITemplateRDFQuery : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITEMPLATERDFQUERY_IID)
+
+ // return the processor the query was created from
+ virtual nsXULTemplateQueryProcessorRDF* Processor() = 0; // not addrefed
+
+ // return the member variable for the query
+ virtual nsIAtom* GetMemberVariable() = 0; // not addrefed
+
+ // return the <query> node the query was compiled from
+ virtual void GetQueryNode(nsIDOMNode** aQueryNode) = 0;
+
+ // remove any results that are cached by the query
+ virtual void ClearCachedResults() = 0;
+};
+
+class nsRDFQuery final : public nsITemplateRDFQuery
+{
+ ~nsRDFQuery() { Finish(); }
+
+public:
+
+ explicit nsRDFQuery(nsXULTemplateQueryProcessorRDF* aProcessor)
+ : mProcessor(aProcessor),
+ mSimple(false),
+ mRoot(nullptr),
+ mCachedResults(nullptr)
+ { }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsRDFQuery)
+
+ /**
+ * Retrieve the root node in the rule network
+ * @return the root node in the rule network
+ */
+ TestNode* GetRoot() { return mRoot; }
+
+ void SetRoot(TestNode* aRoot) { mRoot = aRoot; }
+
+ void GetQueryNode(nsIDOMNode** aQueryNode) override
+ {
+ *aQueryNode = mQueryNode;
+ NS_IF_ADDREF(*aQueryNode);
+ }
+
+ void SetQueryNode(nsIDOMNode* aQueryNode)
+ {
+ mQueryNode = aQueryNode;
+ }
+
+ // an optimization is used when several queries all use the simple query
+ // syntax. Since simple queries can only generate one possible set of
+ // results, they only need to be calculated once and reused for every
+ // simple query. The results may be cached in the query for this purpose.
+ // If successful, this method takes ownership of aInstantiations.
+ nsresult SetCachedResults(nsXULTemplateQueryProcessorRDF* aProcessor,
+ const InstantiationSet& aInstantiations);
+
+ // grab the cached results, if any, causing the caller to take ownership
+ // of them. This also has the effect of setting the cached results in this
+ // nsRDFQuery to null.
+ void UseCachedResults(nsISimpleEnumerator** aResults);
+
+ // clear the cached results
+ void ClearCachedResults() override
+ {
+ mCachedResults = nullptr;
+ }
+
+ nsXULTemplateQueryProcessorRDF* Processor() override { return mProcessor; }
+
+ nsIAtom* GetMemberVariable() override { return mMemberVariable; }
+
+ bool IsSimple() { return mSimple; }
+
+ void SetSimple() { mSimple = true; }
+
+ // the reference and member variables for the query
+ nsCOMPtr<nsIAtom> mRefVariable;
+ nsCOMPtr<nsIAtom> mMemberVariable;
+
+protected:
+
+ nsXULTemplateQueryProcessorRDF* mProcessor;
+
+ // true if the query is a simple rule (one with a default query)
+ bool mSimple;
+
+ /**
+ * The root node in the network for this query
+ */
+ TestNode *mRoot;
+
+ // the <query> node
+ nsCOMPtr<nsIDOMNode> mQueryNode;
+
+ // used for simple rules since their results are all determined in one step
+ nsCOMPtr<nsISimpleEnumerator> mCachedResults;
+
+ void Finish();
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsITemplateRDFQuery, NS_ITEMPLATERDFQUERY_IID)
+
+#endif // nsRDFQuery_h__
diff --git a/dom/xul/templates/nsRDFTestNode.h b/dom/xul/templates/nsRDFTestNode.h
new file mode 100644
index 000000000..0e450fadf
--- /dev/null
+++ b/dom/xul/templates/nsRDFTestNode.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsRDFTestNode_h__
+#define nsRDFTestNode_h__
+
+#include "nsRuleNetwork.h"
+
+class nsIRDFResource;
+class nsIRDFNode;
+
+/**
+ * An abstract base class for all of the RDF-related tests. This interface
+ * allows us to iterate over all of the RDF tests to find the one in the
+ * network that is apropos for a newly-added assertion.
+ */
+class nsRDFTestNode : public TestNode
+{
+public:
+ explicit nsRDFTestNode(TestNode* aParent)
+ : TestNode(aParent) {}
+
+ /**
+ * Determine whether the node can propagate an assertion
+ * with the specified source, property, and target. If the
+ * assertion can be propagated, aInitialBindings will be
+ * initialized with appropriate variable-to-value assignments
+ * to allow the rule network to start a constrain and propagate
+ * search from this node in the network.
+ *
+ * @return true if the node can propagate the specified
+ * assertion.
+ */
+ virtual bool CanPropagate(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ Instantiation& aInitialBindings) const = 0;
+
+ /**
+ *
+ */
+ virtual void Retract(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget) const = 0;
+};
+
+#endif // nsRDFTestNode_h__
diff --git a/dom/xul/templates/nsResourceSet.cpp b/dom/xul/templates/nsResourceSet.cpp
new file mode 100644
index 000000000..64f4aad19
--- /dev/null
+++ b/dom/xul/templates/nsResourceSet.cpp
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsResourceSet.h"
+
+nsResourceSet::nsResourceSet(const nsResourceSet& aResourceSet)
+ : mResources(nullptr),
+ mCount(0),
+ mCapacity(0)
+{
+ ConstIterator last = aResourceSet.Last();
+ for (ConstIterator resource = aResourceSet.First(); resource != last; ++resource)
+ Add(*resource);
+}
+
+
+nsResourceSet&
+nsResourceSet::operator=(const nsResourceSet& aResourceSet)
+{
+ Clear();
+ ConstIterator last = aResourceSet.Last();
+ for (ConstIterator resource = aResourceSet.First(); resource != last; ++resource)
+ Add(*resource);
+ return *this;
+}
+
+nsResourceSet::~nsResourceSet()
+{
+ MOZ_COUNT_DTOR(nsResourceSet);
+ Clear();
+ delete[] mResources;
+}
+
+nsresult
+nsResourceSet::Clear()
+{
+ while (--mCount >= 0) {
+ NS_RELEASE(mResources[mCount]);
+ }
+ mCount = 0;
+ return NS_OK;
+}
+
+nsresult
+nsResourceSet::Add(nsIRDFResource* aResource)
+{
+ NS_PRECONDITION(aResource != nullptr, "null ptr");
+ if (! aResource)
+ return NS_ERROR_NULL_POINTER;
+
+ if (Contains(aResource))
+ return NS_OK;
+
+ if (mCount >= mCapacity) {
+ int32_t capacity = mCapacity + 4;
+ nsIRDFResource** resources = new nsIRDFResource*[capacity];
+ for (int32_t i = mCount - 1; i >= 0; --i)
+ resources[i] = mResources[i];
+
+ delete[] mResources;
+
+ mResources = resources;
+ mCapacity = capacity;
+ }
+
+ mResources[mCount++] = aResource;
+ NS_ADDREF(aResource);
+ return NS_OK;
+}
+
+void
+nsResourceSet::Remove(nsIRDFResource* aProperty)
+{
+ bool found = false;
+
+ nsIRDFResource** res = mResources;
+ nsIRDFResource** limit = mResources + mCount;
+ while (res < limit) {
+ if (found) {
+ *(res - 1) = *res;
+ }
+ else if (*res == aProperty) {
+ NS_RELEASE(*res);
+ found = true;
+ }
+ ++res;
+ }
+
+ if (found)
+ --mCount;
+}
+
+bool
+nsResourceSet::Contains(nsIRDFResource* aResource) const
+{
+ for (int32_t i = mCount - 1; i >= 0; --i) {
+ if (mResources[i] == aResource)
+ return true;
+ }
+
+ return false;
+}
+
diff --git a/dom/xul/templates/nsResourceSet.h b/dom/xul/templates/nsResourceSet.h
new file mode 100644
index 000000000..1f50538a8
--- /dev/null
+++ b/dom/xul/templates/nsResourceSet.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsResourceSet_h__
+#define nsResourceSet_h__
+
+#include "nsIRDFResource.h"
+
+class nsResourceSet
+{
+public:
+ nsResourceSet()
+ : mResources(nullptr),
+ mCount(0),
+ mCapacity(0) {
+ MOZ_COUNT_CTOR(nsResourceSet); }
+
+ nsResourceSet(const nsResourceSet& aResourceSet);
+
+ nsResourceSet& operator=(const nsResourceSet& aResourceSet);
+
+ ~nsResourceSet();
+
+ nsresult Clear();
+ nsresult Add(nsIRDFResource* aProperty);
+ void Remove(nsIRDFResource* aProperty);
+
+ bool Contains(nsIRDFResource* aProperty) const;
+
+protected:
+ nsIRDFResource** mResources;
+ int32_t mCount;
+ int32_t mCapacity;
+
+public:
+ class ConstIterator {
+ protected:
+ nsIRDFResource** mCurrent;
+
+ public:
+ ConstIterator() : mCurrent(nullptr) {}
+
+ ConstIterator(const ConstIterator& aConstIterator)
+ : mCurrent(aConstIterator.mCurrent) {}
+
+ ConstIterator& operator=(const ConstIterator& aConstIterator) {
+ mCurrent = aConstIterator.mCurrent;
+ return *this; }
+
+ ConstIterator& operator++() {
+ ++mCurrent;
+ return *this; }
+
+ ConstIterator operator++(int) {
+ ConstIterator result(*this);
+ ++mCurrent;
+ return result; }
+
+ /*const*/ nsIRDFResource* operator*() const {
+ return *mCurrent; }
+
+ /*const*/ nsIRDFResource* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN {
+ return *mCurrent; }
+
+ bool operator==(const ConstIterator& aConstIterator) const {
+ return mCurrent == aConstIterator.mCurrent; }
+
+ bool operator!=(const ConstIterator& aConstIterator) const {
+ return mCurrent != aConstIterator.mCurrent; }
+
+ protected:
+ explicit ConstIterator(nsIRDFResource** aProperty) : mCurrent(aProperty) {}
+ friend class nsResourceSet;
+ };
+
+ ConstIterator First() const { return ConstIterator(mResources); }
+ ConstIterator Last() const { return ConstIterator(mResources + mCount); }
+};
+
+#endif // nsResourceSet_h__
+
diff --git a/dom/xul/templates/nsRuleNetwork.cpp b/dom/xul/templates/nsRuleNetwork.cpp
new file mode 100644
index 000000000..c2cee5bbd
--- /dev/null
+++ b/dom/xul/templates/nsRuleNetwork.cpp
@@ -0,0 +1,428 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+
+ Implementations for the rule network classes.
+
+ To Do.
+
+ - Constrain() & Propagate() still feel like they are poorly named.
+ - As do Instantiation and InstantiationSet.
+ - Make InstantiationSet share and do copy-on-write.
+ - Make things iterative, instead of recursive.
+
+ */
+
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "plhash.h"
+
+#include "mozilla/Logging.h"
+
+#include "nsString.h"
+#include "nsUnicharUtils.h"
+#include "nsXULContentUtils.h"
+
+#include "nsRuleNetwork.h"
+#include "nsXULTemplateResultSetRDF.h"
+#include "nsRDFConMemberTestNode.h"
+#include "nsRDFPropertyTestNode.h"
+
+using namespace mozilla;
+extern LazyLogModule gXULTemplateLog;
+
+//----------------------------------------------------------------------
+//
+// nsRuleNetwork
+//
+
+nsresult
+MemoryElementSet::Add(MemoryElement* aElement)
+{
+ for (ConstIterator element = First(); element != Last(); ++element) {
+ if (*element == *aElement) {
+ // We've already got this element covered. Since Add()
+ // assumes ownership, and we aren't going to need this,
+ // just nuke it.
+ delete aElement;
+ return NS_OK;
+ }
+ }
+
+ List* list = new List;
+ list->mElement = aElement;
+ list->mRefCnt = 1;
+ list->mNext = mElements;
+
+ mElements = list;
+
+ return NS_OK;
+}
+
+
+//----------------------------------------------------------------------
+
+nsresult
+nsAssignmentSet::Add(const nsAssignment& aAssignment)
+{
+ NS_PRECONDITION(! HasAssignmentFor(aAssignment.mVariable), "variable already bound");
+
+ // XXXndeakin should this just silently fail?
+ if (HasAssignmentFor(aAssignment.mVariable))
+ return NS_ERROR_UNEXPECTED;
+
+ List* list = new List(aAssignment);
+ list->mRefCnt = 1;
+ list->mNext = mAssignments;
+
+ mAssignments = list;
+
+ return NS_OK;
+}
+
+int32_t
+nsAssignmentSet::Count() const
+{
+ int32_t count = 0;
+ for (ConstIterator assignment = First(); assignment != Last(); ++assignment)
+ ++count;
+
+ return count;
+}
+
+bool
+nsAssignmentSet::HasAssignment(nsIAtom* aVariable, nsIRDFNode* aValue) const
+{
+ for (ConstIterator assignment = First(); assignment != Last(); ++assignment) {
+ if (assignment->mVariable == aVariable && assignment->mValue == aValue)
+ return true;
+ }
+
+ return false;
+}
+
+bool
+nsAssignmentSet::HasAssignmentFor(nsIAtom* aVariable) const
+{
+ for (ConstIterator assignment = First(); assignment != Last(); ++assignment) {
+ if (assignment->mVariable == aVariable)
+ return true;
+ }
+
+ return false;
+}
+
+bool
+nsAssignmentSet::GetAssignmentFor(nsIAtom* aVariable, nsIRDFNode** aValue) const
+{
+ for (ConstIterator assignment = First(); assignment != Last(); ++assignment) {
+ if (assignment->mVariable == aVariable) {
+ *aValue = assignment->mValue;
+ NS_IF_ADDREF(*aValue);
+ return true;
+ }
+ }
+
+ *aValue = nullptr;
+ return false;
+}
+
+bool
+nsAssignmentSet::Equals(const nsAssignmentSet& aSet) const
+{
+ if (aSet.mAssignments == mAssignments)
+ return true;
+
+ // If they have a different number of assignments, then they're different.
+ if (Count() != aSet.Count())
+ return false;
+
+ // XXX O(n^2)! Ugh!
+ nsCOMPtr<nsIRDFNode> value;
+ for (ConstIterator assignment = First(); assignment != Last(); ++assignment) {
+ if (! aSet.GetAssignmentFor(assignment->mVariable, getter_AddRefs(value)))
+ return false;
+
+ if (assignment->mValue != value)
+ return false;
+ }
+
+ return true;
+}
+
+//----------------------------------------------------------------------
+
+PLHashNumber
+Instantiation::Hash(const void* aKey)
+{
+ const Instantiation* inst = static_cast<const Instantiation*>(aKey);
+
+ PLHashNumber result = 0;
+
+ nsAssignmentSet::ConstIterator last = inst->mAssignments.Last();
+ for (nsAssignmentSet::ConstIterator assignment = inst->mAssignments.First();
+ assignment != last; ++assignment)
+ result ^= assignment->Hash();
+
+ return result;
+}
+
+
+int
+Instantiation::Compare(const void* aLeft, const void* aRight)
+{
+ const Instantiation* left = static_cast<const Instantiation*>(aLeft);
+ const Instantiation* right = static_cast<const Instantiation*>(aRight);
+
+ return *left == *right;
+}
+
+
+//----------------------------------------------------------------------
+//
+// InstantiationSet
+//
+
+InstantiationSet::InstantiationSet()
+{
+ mHead.mPrev = mHead.mNext = &mHead;
+ MOZ_COUNT_CTOR(InstantiationSet);
+}
+
+
+InstantiationSet::InstantiationSet(const InstantiationSet& aInstantiationSet)
+{
+ mHead.mPrev = mHead.mNext = &mHead;
+
+ // XXX replace with copy-on-write foo
+ ConstIterator last = aInstantiationSet.Last();
+ for (ConstIterator inst = aInstantiationSet.First(); inst != last; ++inst)
+ Append(*inst);
+
+ MOZ_COUNT_CTOR(InstantiationSet);
+}
+
+InstantiationSet&
+InstantiationSet::operator=(const InstantiationSet& aInstantiationSet)
+{
+ // XXX replace with copy-on-write foo
+ Clear();
+
+ ConstIterator last = aInstantiationSet.Last();
+ for (ConstIterator inst = aInstantiationSet.First(); inst != last; ++inst)
+ Append(*inst);
+
+ return *this;
+}
+
+
+void
+InstantiationSet::Clear()
+{
+ Iterator inst = First();
+ while (inst != Last())
+ Erase(inst++);
+}
+
+
+InstantiationSet::Iterator
+InstantiationSet::Insert(Iterator aIterator, const Instantiation& aInstantiation)
+{
+ List* newelement = new List();
+ if (newelement) {
+ newelement->mInstantiation = aInstantiation;
+
+ aIterator.mCurrent->mPrev->mNext = newelement;
+
+ newelement->mNext = aIterator.mCurrent;
+ newelement->mPrev = aIterator.mCurrent->mPrev;
+
+ aIterator.mCurrent->mPrev = newelement;
+ }
+ return aIterator;
+}
+
+InstantiationSet::Iterator
+InstantiationSet::Erase(Iterator aIterator)
+{
+ Iterator result = aIterator;
+ ++result;
+ aIterator.mCurrent->mNext->mPrev = aIterator.mCurrent->mPrev;
+ aIterator.mCurrent->mPrev->mNext = aIterator.mCurrent->mNext;
+ delete aIterator.mCurrent;
+ return result;
+}
+
+
+bool
+InstantiationSet::HasAssignmentFor(nsIAtom* aVariable) const
+{
+ return !Empty() ? First()->mAssignments.HasAssignmentFor(aVariable) : false;
+}
+
+//----------------------------------------------------------------------
+//
+// ReteNode
+//
+// The basic node in the network.
+//
+
+//----------------------------------------------------------------------
+//
+// TestNode
+//
+// to do:
+// - FilterInstantiations() is poorly named
+//
+
+
+TestNode::TestNode(TestNode* aParent)
+ : mParent(aParent)
+{
+}
+
+nsresult
+TestNode::Propagate(InstantiationSet& aInstantiations,
+ bool aIsUpdate, bool& aTakenInstantiations)
+{
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("TestNode[%p]: Propagate() begin", this));
+
+ aTakenInstantiations = false;
+
+ nsresult rv = FilterInstantiations(aInstantiations, nullptr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // if there is more than one child, each will need to be supplied with the
+ // original set of instantiations from this node, so create a copy in this
+ // case. If there is only one child, optimize and just pass the
+ // instantiations along to the child without copying
+ bool shouldCopy = (mKids.Count() > 1);
+
+ // See the header file for details about how instantiation ownership works.
+ if (! aInstantiations.Empty()) {
+ ReteNodeSet::Iterator last = mKids.Last();
+ for (ReteNodeSet::Iterator kid = mKids.First(); kid != last; ++kid) {
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("TestNode[%p]: Propagate() passing to child %p", this, kid.operator->()));
+
+ // create a copy of the instantiations
+ if (shouldCopy) {
+ bool owned = false;
+ InstantiationSet* instantiations =
+ new InstantiationSet(aInstantiations);
+ rv = kid->Propagate(*instantiations, aIsUpdate, owned);
+ if (!owned)
+ delete instantiations;
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ else {
+ rv = kid->Propagate(aInstantiations, aIsUpdate, aTakenInstantiations);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ }
+ }
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("TestNode[%p]: Propagate() end", this));
+
+ return NS_OK;
+}
+
+
+nsresult
+TestNode::Constrain(InstantiationSet& aInstantiations)
+{
+ nsresult rv;
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("TestNode[%p]: Constrain() begin", this));
+
+ // if the cantHandleYet flag is set by FilterInstantiations,
+ // there isn't enough information yet available to fill in.
+ // For this, continue the constrain all the way to the top
+ // and then call FilterInstantiations again afterwards. This
+ // should fill in any missing information.
+ bool cantHandleYet = false;
+ rv = FilterInstantiations(aInstantiations, &cantHandleYet);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mParent && (!aInstantiations.Empty() || cantHandleYet)) {
+ // if we still have instantiations, or if the instantiations
+ // could not be filled in yet, then ride 'em on up to the
+ // parent to narrow them.
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("TestNode[%p]: Constrain() passing to parent %p", this, mParent));
+
+ rv = mParent->Constrain(aInstantiations);
+
+ if (NS_SUCCEEDED(rv) && cantHandleYet)
+ rv = FilterInstantiations(aInstantiations, nullptr);
+ }
+ else {
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("TestNode[%p]: Constrain() failed", this));
+
+ rv = NS_OK;
+ }
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("TestNode[%p]: Constrain() end", this));
+
+ return rv;
+}
+
+
+//----------------------------------------------------------------------
+
+ReteNodeSet::ReteNodeSet()
+ : mNodes(nullptr), mCount(0), mCapacity(0)
+{
+}
+
+ReteNodeSet::~ReteNodeSet()
+{
+ Clear();
+}
+
+nsresult
+ReteNodeSet::Add(ReteNode* aNode)
+{
+ NS_PRECONDITION(aNode != nullptr, "null ptr");
+ if (! aNode)
+ return NS_ERROR_NULL_POINTER;
+
+ if (mCount >= mCapacity) {
+ int32_t capacity = mCapacity + 4;
+ ReteNode** nodes = new ReteNode*[capacity];
+ if (! nodes)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ for (int32_t i = mCount - 1; i >= 0; --i)
+ nodes[i] = mNodes[i];
+
+ delete[] mNodes;
+
+ mNodes = nodes;
+ mCapacity = capacity;
+ }
+
+ mNodes[mCount++] = aNode;
+ return NS_OK;
+}
+
+nsresult
+ReteNodeSet::Clear()
+{
+ delete[] mNodes;
+ mNodes = nullptr;
+ mCount = mCapacity = 0;
+ return NS_OK;
+}
diff --git a/dom/xul/templates/nsRuleNetwork.h b/dom/xul/templates/nsRuleNetwork.h
new file mode 100644
index 000000000..2b4402722
--- /dev/null
+++ b/dom/xul/templates/nsRuleNetwork.h
@@ -0,0 +1,861 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+
+ A rule discrimination network implementation based on ideas from
+ RETE and TREAT.
+
+ RETE is described in Charles Forgy, "Rete: A Fast Algorithm for the
+ Many Patterns/Many Objects Match Problem", Artificial Intelligence
+ 19(1): pp. 17-37, 1982.
+
+ TREAT is described in Daniel P. Miranker, "TREAT: A Better Match
+ Algorithm for AI Production System Matching", AAAI 1987: pp. 42-47.
+
+ --
+
+ TO DO:
+
+ . nsAssignmentSet::List objects are allocated by the gallon. We
+ should make it so that these are always allocated from a pool,
+ maybe owned by the nsRuleNetwork?
+
+ */
+
+#ifndef nsRuleNetwork_h__
+#define nsRuleNetwork_h__
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsIAtom.h"
+#include "nsIDOMNode.h"
+#include "plhash.h"
+#include "PLDHashTable.h"
+#include "nsIRDFNode.h"
+
+class nsXULTemplateResultSetRDF;
+
+//----------------------------------------------------------------------
+
+/**
+ * A memory element that supports an instantiation. A memory element holds a
+ * set of nodes involved in an RDF test such as <member> or <triple> test. A
+ * memory element is created when a specific test matches. The query processor
+ * maintains a map between the memory elements and the results they eventually
+ * matched. When an assertion is removed from the graph, this map is consulted
+ * to determine which results will no longer match.
+ */
+class MemoryElement {
+protected:
+ MemoryElement() { MOZ_COUNT_CTOR(MemoryElement); }
+
+public:
+ virtual ~MemoryElement() { MOZ_COUNT_DTOR(MemoryElement); }
+
+ virtual const char* Type() const = 0;
+ virtual PLHashNumber Hash() const = 0;
+ virtual bool Equals(const MemoryElement& aElement) const = 0;
+
+ bool operator==(const MemoryElement& aMemoryElement) const {
+ return Equals(aMemoryElement);
+ }
+
+ bool operator!=(const MemoryElement& aMemoryElement) const {
+ return !Equals(aMemoryElement);
+ }
+};
+
+//----------------------------------------------------------------------
+
+/**
+ * A collection of memory elements
+ */
+class MemoryElementSet {
+public:
+ class ConstIterator;
+ friend class ConstIterator;
+
+protected:
+ class List {
+ public:
+ List() { MOZ_COUNT_CTOR(MemoryElementSet::List); }
+
+ protected:
+ ~List() {
+ MOZ_COUNT_DTOR(MemoryElementSet::List);
+ delete mElement;
+ NS_IF_RELEASE(mNext); }
+
+ public:
+ int32_t AddRef() { return ++mRefCnt; }
+
+ int32_t Release() {
+ int32_t refcnt = --mRefCnt;
+ if (refcnt == 0) delete this;
+ return refcnt; }
+
+ MemoryElement* mElement;
+ int32_t mRefCnt;
+ List* mNext;
+ };
+
+ List* mElements;
+
+public:
+ MemoryElementSet() : mElements(nullptr) {
+ MOZ_COUNT_CTOR(MemoryElementSet); }
+
+ MemoryElementSet(const MemoryElementSet& aSet) : mElements(aSet.mElements) {
+ MOZ_COUNT_CTOR(MemoryElementSet);
+ NS_IF_ADDREF(mElements); }
+
+ MemoryElementSet& operator=(const MemoryElementSet& aSet) {
+ NS_IF_RELEASE(mElements);
+ mElements = aSet.mElements;
+ NS_IF_ADDREF(mElements);
+ return *this; }
+
+ ~MemoryElementSet() {
+ MOZ_COUNT_DTOR(MemoryElementSet);
+ NS_IF_RELEASE(mElements); }
+
+public:
+ class ConstIterator {
+ public:
+ explicit ConstIterator(List* aElementList) : mCurrent(aElementList) {
+ NS_IF_ADDREF(mCurrent); }
+
+ ConstIterator(const ConstIterator& aConstIterator)
+ : mCurrent(aConstIterator.mCurrent) {
+ NS_IF_ADDREF(mCurrent); }
+
+ ConstIterator& operator=(const ConstIterator& aConstIterator) {
+ NS_IF_RELEASE(mCurrent);
+ mCurrent = aConstIterator.mCurrent;
+ NS_IF_ADDREF(mCurrent);
+ return *this; }
+
+ ~ConstIterator() { NS_IF_RELEASE(mCurrent); }
+
+ ConstIterator& operator++() {
+ List* next = mCurrent->mNext;
+ NS_RELEASE(mCurrent);
+ mCurrent = next;
+ NS_IF_ADDREF(mCurrent);
+ return *this; }
+
+ ConstIterator operator++(int) {
+ ConstIterator result(*this);
+ List* next = mCurrent->mNext;
+ NS_RELEASE(mCurrent);
+ mCurrent = next;
+ NS_IF_ADDREF(mCurrent);
+ return result; }
+
+ const MemoryElement& operator*() const {
+ return *mCurrent->mElement; }
+
+ const MemoryElement* operator->() const {
+ return mCurrent->mElement; }
+
+ bool operator==(const ConstIterator& aConstIterator) const {
+ return mCurrent == aConstIterator.mCurrent; }
+
+ bool operator!=(const ConstIterator& aConstIterator) const {
+ return mCurrent != aConstIterator.mCurrent; }
+
+ protected:
+ List* mCurrent;
+ };
+
+ ConstIterator First() const { return ConstIterator(mElements); }
+ ConstIterator Last() const { return ConstIterator(nullptr); }
+
+ // N.B. that the set assumes ownership of the element
+ nsresult Add(MemoryElement* aElement);
+};
+
+//----------------------------------------------------------------------
+
+/**
+ * An assignment of a value to a variable
+ */
+class nsAssignment {
+public:
+ const nsCOMPtr<nsIAtom> mVariable;
+ nsCOMPtr<nsIRDFNode> mValue;
+
+ nsAssignment(nsIAtom* aVariable, nsIRDFNode* aValue)
+ : mVariable(aVariable),
+ mValue(aValue)
+ { MOZ_COUNT_CTOR(nsAssignment); }
+
+ nsAssignment(const nsAssignment& aAssignment)
+ : mVariable(aAssignment.mVariable),
+ mValue(aAssignment.mValue)
+ { MOZ_COUNT_CTOR(nsAssignment); }
+
+ ~nsAssignment() { MOZ_COUNT_DTOR(nsAssignment); }
+
+ bool operator==(const nsAssignment& aAssignment) const {
+ return mVariable == aAssignment.mVariable && mValue == aAssignment.mValue; }
+
+ bool operator!=(const nsAssignment& aAssignment) const {
+ return mVariable != aAssignment.mVariable || mValue != aAssignment.mValue; }
+
+ PLHashNumber Hash() const {
+ // XXX I have no idea if this hashing function is good or not // XXX change this
+ PLHashNumber temp = PLHashNumber(NS_PTR_TO_INT32(mValue.get())) >> 2; // strip alignment bits
+ return (temp & 0xffff) | NS_PTR_TO_INT32(mVariable.get()); }
+};
+
+
+//----------------------------------------------------------------------
+
+/**
+ * A collection of value-to-variable assignments that minimizes
+ * copying by sharing subsets when possible.
+ */
+class nsAssignmentSet {
+public:
+ class ConstIterator;
+ friend class ConstIterator;
+
+protected:
+ class List {
+ public:
+ explicit List(const nsAssignment& aAssignment) : mAssignment(aAssignment) {
+ MOZ_COUNT_CTOR(nsAssignmentSet::List); }
+
+ protected:
+ ~List() {
+ MOZ_COUNT_DTOR(nsAssignmentSet::List);
+ NS_IF_RELEASE(mNext); }
+
+ public:
+
+ int32_t AddRef() { return ++mRefCnt; }
+
+ int32_t Release() {
+ int32_t refcnt = --mRefCnt;
+ if (refcnt == 0) delete this;
+ return refcnt; }
+
+ nsAssignment mAssignment;
+ int32_t mRefCnt;
+ List* mNext;
+ };
+
+ List* mAssignments;
+
+public:
+ nsAssignmentSet()
+ : mAssignments(nullptr)
+ { MOZ_COUNT_CTOR(nsAssignmentSet); }
+
+ nsAssignmentSet(const nsAssignmentSet& aSet)
+ : mAssignments(aSet.mAssignments) {
+ MOZ_COUNT_CTOR(nsAssignmentSet);
+ NS_IF_ADDREF(mAssignments); }
+
+ nsAssignmentSet& operator=(const nsAssignmentSet& aSet) {
+ NS_IF_RELEASE(mAssignments);
+ mAssignments = aSet.mAssignments;
+ NS_IF_ADDREF(mAssignments);
+ return *this; }
+
+ ~nsAssignmentSet() {
+ MOZ_COUNT_DTOR(nsAssignmentSet);
+ NS_IF_RELEASE(mAssignments); }
+
+public:
+ class ConstIterator {
+ public:
+ explicit ConstIterator(List* aAssignmentList) : mCurrent(aAssignmentList) {
+ NS_IF_ADDREF(mCurrent); }
+
+ ConstIterator(const ConstIterator& aConstIterator)
+ : mCurrent(aConstIterator.mCurrent) {
+ NS_IF_ADDREF(mCurrent); }
+
+ ConstIterator& operator=(const ConstIterator& aConstIterator) {
+ NS_IF_RELEASE(mCurrent);
+ mCurrent = aConstIterator.mCurrent;
+ NS_IF_ADDREF(mCurrent);
+ return *this; }
+
+ ~ConstIterator() { NS_IF_RELEASE(mCurrent); }
+
+ ConstIterator& operator++() {
+ List* next = mCurrent->mNext;
+ NS_RELEASE(mCurrent);
+ mCurrent = next;
+ NS_IF_ADDREF(mCurrent);
+ return *this; }
+
+ ConstIterator operator++(int) {
+ ConstIterator result(*this);
+ List* next = mCurrent->mNext;
+ NS_RELEASE(mCurrent);
+ mCurrent = next;
+ NS_IF_ADDREF(mCurrent);
+ return result; }
+
+ const nsAssignment& operator*() const {
+ return mCurrent->mAssignment; }
+
+ const nsAssignment* operator->() const {
+ return &mCurrent->mAssignment; }
+
+ bool operator==(const ConstIterator& aConstIterator) const {
+ return mCurrent == aConstIterator.mCurrent; }
+
+ bool operator!=(const ConstIterator& aConstIterator) const {
+ return mCurrent != aConstIterator.mCurrent; }
+
+ protected:
+ List* mCurrent;
+ };
+
+ ConstIterator First() const { return ConstIterator(mAssignments); }
+ ConstIterator Last() const { return ConstIterator(nullptr); }
+
+public:
+ /**
+ * Add an assignment to the set
+ * @param aElement the assigment to add
+ * @return NS_OK if all is well, NS_ERROR_OUT_OF_MEMORY if memory
+ * could not be allocated for the addition.
+ */
+ nsresult Add(const nsAssignment& aElement);
+
+ /**
+ * Determine if the assignment set contains the specified variable
+ * to value assignment.
+ * @param aVariable the variable for which to lookup the binding
+ * @param aValue the value to query
+ * @return true if aVariable is bound to aValue; false otherwise.
+ */
+ bool HasAssignment(nsIAtom* aVariable, nsIRDFNode* aValue) const;
+
+ /**
+ * Determine if the assignment set contains the specified assignment
+ * @param aAssignment the assignment to search for
+ * @return true if the set contains the assignment, false otherwise.
+ */
+ bool HasAssignment(const nsAssignment& aAssignment) const {
+ return HasAssignment(aAssignment.mVariable, aAssignment.mValue); }
+
+ /**
+ * Determine whether the assignment set has an assignment for the
+ * specified variable.
+ * @param aVariable the variable to query
+ * @return true if the assignment set has an assignment for the variable,
+ * false otherwise.
+ */
+ bool HasAssignmentFor(nsIAtom* aVariable) const;
+
+ /**
+ * Retrieve the assignment for the specified variable
+ * @param aVariable the variable to query
+ * @param aValue an out parameter that will receive the value assigned
+ * to the variable, if any.
+ * @return true if the variable has an assignment, false
+ * if there was no assignment for the variable.
+ */
+ bool GetAssignmentFor(nsIAtom* aVariable, nsIRDFNode** aValue) const;
+
+ /**
+ * Count the number of assignments in the set
+ * @return the number of assignments in the set
+ */
+ int32_t Count() const;
+
+ /**
+ * Determine if the set is empty
+ * @return true if the assignment set is empty, false otherwise.
+ */
+ bool IsEmpty() const { return mAssignments == nullptr; }
+
+ bool Equals(const nsAssignmentSet& aSet) const;
+ bool operator==(const nsAssignmentSet& aSet) const { return Equals(aSet); }
+ bool operator!=(const nsAssignmentSet& aSet) const { return !Equals(aSet); }
+};
+
+
+//----------------------------------------------------------------------
+
+/**
+ * A collection of variable-to-value bindings, with the memory elements
+ * that support those bindings. Essentially, an instantiation is the
+ * collection of variables and values assigned to those variables for a single
+ * result. For each RDF rule in the rule network, each instantiation is
+ * examined and either extended with additional bindings specified by the RDF
+ * rule, or removed if the rule doesn't apply (for instance if a node has no
+ * children). When an instantiation gets to the last node of the rule network,
+ * which is always an nsInstantiationNode, a result is created for it.
+ *
+ * An instantiation object is typically created by "extending" another
+ * instantiation object. That is, using the copy constructor, and
+ * adding bindings and support to the instantiation.
+ */
+class Instantiation
+{
+public:
+ /**
+ * The variable-to-value bindings
+ */
+ nsAssignmentSet mAssignments;
+
+ /**
+ * The memory elements that support the bindings.
+ */
+ MemoryElementSet mSupport;
+
+ Instantiation() { MOZ_COUNT_CTOR(Instantiation); }
+
+ Instantiation(const Instantiation& aInstantiation)
+ : mAssignments(aInstantiation.mAssignments),
+ mSupport(aInstantiation.mSupport) {
+ MOZ_COUNT_CTOR(Instantiation); }
+
+ Instantiation& operator=(const Instantiation& aInstantiation) {
+ mAssignments = aInstantiation.mAssignments;
+ mSupport = aInstantiation.mSupport;
+ return *this; }
+
+ ~Instantiation() { MOZ_COUNT_DTOR(Instantiation); }
+
+ /**
+ * Add the specified variable-to-value assignment to the instantiation's
+ * set of assignments.
+ * @param aVariable the variable to which is being assigned
+ * @param aValue the value that is being assigned
+ * @return NS_OK if no errors, NS_ERROR_OUT_OF_MEMORY if there
+ * is not enough memory to perform the operation
+ */
+ nsresult AddAssignment(nsIAtom* aVariable, nsIRDFNode* aValue) {
+ mAssignments.Add(nsAssignment(aVariable, aValue));
+ return NS_OK; }
+
+ /**
+ * Add a memory element to the set of memory elements that are
+ * supporting the instantiation
+ * @param aMemoryElement the memory element to add to the
+ * instantiation's set of support
+ * @return NS_OK if no errors occurred, NS_ERROR_OUT_OF_MEMORY
+ * if there is not enough memory to perform the operation.
+ */
+ nsresult AddSupportingElement(MemoryElement* aMemoryElement) {
+ mSupport.Add(aMemoryElement);
+ return NS_OK; }
+
+ bool Equals(const Instantiation& aInstantiation) const {
+ return mAssignments == aInstantiation.mAssignments; }
+
+ bool operator==(const Instantiation& aInstantiation) const {
+ return Equals(aInstantiation); }
+
+ bool operator!=(const Instantiation& aInstantiation) const {
+ return !Equals(aInstantiation); }
+
+ static PLHashNumber Hash(const void* aKey);
+ static int Compare(const void* aLeft, const void* aRight);
+};
+
+
+//----------------------------------------------------------------------
+
+/**
+ * A collection of intantiations
+ */
+class InstantiationSet
+{
+public:
+ InstantiationSet();
+ InstantiationSet(const InstantiationSet& aInstantiationSet);
+ InstantiationSet& operator=(const InstantiationSet& aInstantiationSet);
+
+ ~InstantiationSet() {
+ MOZ_COUNT_DTOR(InstantiationSet);
+ Clear(); }
+
+ class ConstIterator;
+ friend class ConstIterator;
+
+ class Iterator;
+ friend class Iterator;
+
+ friend class nsXULTemplateResultSetRDF; // so it can get to the List
+
+protected:
+ class List {
+ public:
+ Instantiation mInstantiation;
+ List* mNext;
+ List* mPrev;
+
+ List() { MOZ_COUNT_CTOR(InstantiationSet::List); }
+ ~List() { MOZ_COUNT_DTOR(InstantiationSet::List); }
+ };
+
+ List mHead;
+
+public:
+ class ConstIterator {
+ protected:
+ friend class Iterator; // XXXwaterson so broken.
+ List* mCurrent;
+
+ public:
+ explicit ConstIterator(List* aList) : mCurrent(aList) {}
+
+ ConstIterator(const ConstIterator& aConstIterator)
+ : mCurrent(aConstIterator.mCurrent) {}
+
+ ConstIterator& operator=(const ConstIterator& aConstIterator) {
+ mCurrent = aConstIterator.mCurrent;
+ return *this; }
+
+ ConstIterator& operator++() {
+ mCurrent = mCurrent->mNext;
+ return *this; }
+
+ ConstIterator operator++(int) {
+ ConstIterator result(*this);
+ mCurrent = mCurrent->mNext;
+ return result; }
+
+ ConstIterator& operator--() {
+ mCurrent = mCurrent->mPrev;
+ return *this; }
+
+ ConstIterator operator--(int) {
+ ConstIterator result(*this);
+ mCurrent = mCurrent->mPrev;
+ return result; }
+
+ const Instantiation& operator*() const {
+ return mCurrent->mInstantiation; }
+
+ const Instantiation* operator->() const {
+ return &mCurrent->mInstantiation; }
+
+ bool operator==(const ConstIterator& aConstIterator) const {
+ return mCurrent == aConstIterator.mCurrent; }
+
+ bool operator!=(const ConstIterator& aConstIterator) const {
+ return mCurrent != aConstIterator.mCurrent; }
+ };
+
+ ConstIterator First() const { return ConstIterator(mHead.mNext); }
+ ConstIterator Last() const { return ConstIterator(const_cast<List*>(&mHead)); }
+
+ class Iterator : public ConstIterator {
+ public:
+ explicit Iterator(List* aList) : ConstIterator(aList) {}
+
+ Iterator& operator++() {
+ mCurrent = mCurrent->mNext;
+ return *this; }
+
+ Iterator operator++(int) {
+ Iterator result(*this);
+ mCurrent = mCurrent->mNext;
+ return result; }
+
+ Iterator& operator--() {
+ mCurrent = mCurrent->mPrev;
+ return *this; }
+
+ Iterator operator--(int) {
+ Iterator result(*this);
+ mCurrent = mCurrent->mPrev;
+ return result; }
+
+ Instantiation& operator*() const {
+ return mCurrent->mInstantiation; }
+
+ Instantiation* operator->() const {
+ return &mCurrent->mInstantiation; }
+
+ bool operator==(const ConstIterator& aConstIterator) const {
+ return mCurrent == aConstIterator.mCurrent; }
+
+ bool operator!=(const ConstIterator& aConstIterator) const {
+ return mCurrent != aConstIterator.mCurrent; }
+
+ friend class InstantiationSet;
+ };
+
+ Iterator First() { return Iterator(mHead.mNext); }
+ Iterator Last() { return Iterator(&mHead); }
+
+ bool Empty() const { return First() == Last(); }
+
+ Iterator Append(const Instantiation& aInstantiation) {
+ return Insert(Last(), aInstantiation); }
+
+ Iterator Insert(Iterator aBefore, const Instantiation& aInstantiation);
+
+ Iterator Erase(Iterator aElement);
+
+ void Clear();
+
+ bool HasAssignmentFor(nsIAtom* aVariable) const;
+};
+
+//----------------------------------------------------------------------
+
+/**
+ * A abstract base class for all nodes in the rule network
+ */
+class ReteNode
+{
+public:
+ ReteNode() {}
+ virtual ~ReteNode() {}
+
+ /**
+ * Propagate a set of instantiations "down" through the
+ * network. Each instantiation is a partial set of
+ * variable-to-value assignments, along with the memory elements
+ * that support it.
+ *
+ * The node must evaluate each instantiation, and either 1)
+ * extend it with additional assignments and memory-element
+ * support, or 2) remove it from the set because it is
+ * inconsistent with the constraints that this node applies.
+ *
+ * The node must then pass the resulting instantiation set along
+ * to any of its children in the network. (In other words, the
+ * node must recursively call Propagate() on its children. We
+ * should fix this to make the algorithm interruptable.)
+ *
+ * See TestNode::Propagate for details about instantiation set ownership
+ *
+ * @param aInstantiations the set of instantiations to propagate
+ * down through the network.
+ * @param aIsUpdate true if updating, false for first generation
+ * @param aTakenInstantiations true if the ownership over aInstantiations
+ * has been taken from the caller. If false,
+ * the caller owns it.
+ * @return NS_OK if no errors occurred.
+ */
+ virtual nsresult Propagate(InstantiationSet& aInstantiations,
+ bool aIsUpdate, bool& aTakenInstantiations) = 0;
+};
+
+//----------------------------------------------------------------------
+
+/**
+ * A collection of nodes in the rule network
+ */
+class ReteNodeSet
+{
+public:
+ ReteNodeSet();
+ ~ReteNodeSet();
+
+ nsresult Add(ReteNode* aNode);
+ nsresult Clear();
+
+ class Iterator;
+
+ class ConstIterator {
+ public:
+ explicit ConstIterator(ReteNode** aNode) : mCurrent(aNode) {}
+
+ ConstIterator(const ConstIterator& aConstIterator)
+ : mCurrent(aConstIterator.mCurrent) {}
+
+ ConstIterator& operator=(const ConstIterator& aConstIterator) {
+ mCurrent = aConstIterator.mCurrent;
+ return *this; }
+
+ ConstIterator& operator++() {
+ ++mCurrent;
+ return *this; }
+
+ ConstIterator operator++(int) {
+ ConstIterator result(*this);
+ ++mCurrent;
+ return result; }
+
+ const ReteNode* operator*() const {
+ return *mCurrent; }
+
+ const ReteNode* operator->() const {
+ return *mCurrent; }
+
+ bool operator==(const ConstIterator& aConstIterator) const {
+ return mCurrent == aConstIterator.mCurrent; }
+
+ bool operator!=(const ConstIterator& aConstIterator) const {
+ return mCurrent != aConstIterator.mCurrent; }
+
+ protected:
+ friend class Iterator; // XXXwaterson this is so wrong!
+ ReteNode** mCurrent;
+ };
+
+ ConstIterator First() const { return ConstIterator(mNodes); }
+ ConstIterator Last() const { return ConstIterator(mNodes + mCount); }
+
+ class Iterator : public ConstIterator {
+ public:
+ explicit Iterator(ReteNode** aNode) : ConstIterator(aNode) {}
+
+ Iterator& operator++() {
+ ++mCurrent;
+ return *this; }
+
+ Iterator operator++(int) {
+ Iterator result(*this);
+ ++mCurrent;
+ return result; }
+
+ ReteNode* operator*() const {
+ return *mCurrent; }
+
+ ReteNode* operator->() const {
+ return *mCurrent; }
+
+ bool operator==(const ConstIterator& aConstIterator) const {
+ return mCurrent == aConstIterator.mCurrent; }
+
+ bool operator!=(const ConstIterator& aConstIterator) const {
+ return mCurrent != aConstIterator.mCurrent; }
+ };
+
+ Iterator First() { return Iterator(mNodes); }
+ Iterator Last() { return Iterator(mNodes + mCount); }
+
+ int32_t Count() const { return mCount; }
+
+protected:
+ ReteNode** mNodes;
+ int32_t mCount;
+ int32_t mCapacity;
+};
+
+//----------------------------------------------------------------------
+
+/**
+ * A node that applies a test condition to a set of instantiations.
+ *
+ * This class provides implementations of Propagate() and Constrain()
+ * in terms of one simple operation, FilterInstantiations(). A node
+ * that is a "simple test node" in a rule network should derive from
+ * this class, and need only implement FilterInstantiations().
+ */
+class TestNode : public ReteNode
+{
+public:
+ explicit TestNode(TestNode* aParent);
+
+ /**
+ * Retrieve the test node's parent
+ * @return the test node's parent
+ */
+ TestNode* GetParent() const { return mParent; }
+
+ /**
+ * Calls FilterInstantiations() on the instantiation set, and if
+ * the resulting set isn't empty, propagates the new set down to
+ * each of the test node's children.
+ *
+ * Note that the caller of Propagate is responsible for deleting
+ * aInstantiations if necessary as described below.
+ *
+ * Propagate may be called in update or non-update mode as indicated
+ * by the aIsUpdate argument. Non-update mode is used when initially
+ * generating results, whereas update mode is used when the datasource
+ * changes and new results might be available.
+ *
+ * The last node in a chain of TestNodes is always an nsInstantiationNode.
+ * In non-update mode, this nsInstantiationNode will cache the results
+ * in the query using the SetCachedResults method. The query processor
+ * takes these cached results and creates a nsXULTemplateResultSetRDF
+ * which is the enumeration returned to the template builder. This
+ * nsXULTemplateResultSetRDF owns the instantiations and they will be
+ * deleted when the nsXULTemplateResultSetRDF goes away.
+ *
+ * In update mode, the nsInstantiationNode node will iterate over the
+ * instantiations itself and callback to the builder to update any matches
+ * and generated content. If no instantiations match, then the builder
+ * will never be called.
+ *
+ * Thus, the difference between update and non-update modes is that in
+ * update mode, the results and instantiations have been already handled
+ * whereas in non-update mode they are expected to be returned in an
+ * nsXULTemplateResultSetRDF for further processing by the builder.
+ *
+ * Regardless, aTakenInstantiations will be set to true if the
+ * ownership over aInstantiations has been transferred to a result set.
+ * If set to false, the caller is still responsible for aInstantiations.
+ * aTakenInstantiations will be set properly even if an error occurs.
+ */
+ virtual nsresult Propagate(InstantiationSet& aInstantiations,
+ bool aIsUpdate, bool& aTakenInstantiations) override;
+
+ /**
+ * This is called by a child node on its parent to allow the
+ * parent's constraints to apply to the set of instantiations.
+ *
+ * A node must iterate through the set of instantiations, and for
+ * each instantiation, either 1) extend the instantiation by
+ * adding variable-to-value assignments and memory element support
+ * for those assignments, or 2) remove the instantiation because
+ * it is inconsistent.
+ *
+ * The node must then pass the resulting set of instantiations up
+ * to its parent (by recursive call; we should make this iterative
+ * & interruptable at some point.)
+ *
+ * @param aInstantiations the set of instantiations that must
+ * be constrained
+ * @return NS_OK if no errors occurred
+ */
+ virtual nsresult Constrain(InstantiationSet& aInstantiations);
+
+ /**
+ * Given a set of instantiations, filter out any that are
+ * inconsistent with the test node's test, and append
+ * variable-to-value assignments and memory element support for
+ * those which do pass the test node's test.
+ *
+ * @param aInstantiations the set of instantiations to be
+ * filtered
+ * @param aCantHandleYet [out] true if the instantiations do not contain
+ * enough information to constrain the data. May be null if this
+ * isn't important to the caller.
+ * @return NS_OK if no errors occurred.
+ */
+ virtual nsresult FilterInstantiations(InstantiationSet& aInstantiations,
+ bool* aCantHandleYet) const = 0;
+ //XXX probably better named "ApplyConstraints" or "Discrminiate" or something
+
+ /**
+ * Add another node as a child of this node.
+ * @param aNode the node to add.
+ * @return NS_OK if no errors occur.
+ */
+ nsresult AddChild(ReteNode* aNode) { return mKids.Add(aNode); }
+
+ /**
+ * Remove all the children of this node
+ * @return NS_OK if no errors occur.
+ */
+ nsresult RemoveAllChildren() { return mKids.Clear(); }
+
+protected:
+ TestNode* mParent;
+ ReteNodeSet mKids;
+};
+
+#endif // nsRuleNetwork_h__
diff --git a/dom/xul/templates/nsTemplateMap.h b/dom/xul/templates/nsTemplateMap.h
new file mode 100644
index 000000000..cb828b093
--- /dev/null
+++ b/dom/xul/templates/nsTemplateMap.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTemplateMap_h__
+#define nsTemplateMap_h__
+
+#include "PLDHashTable.h"
+#include "nsXULElement.h"
+
+class nsTemplateMap {
+protected:
+ struct Entry : public PLDHashEntryHdr {
+ nsIContent* mContent;
+ nsIContent* mTemplate;
+ };
+
+ PLDHashTable mTable;
+
+public:
+ nsTemplateMap() : mTable(PLDHashTable::StubOps(), sizeof(Entry)) { }
+
+ ~nsTemplateMap() { }
+
+ void
+ Put(nsIContent* aContent, nsIContent* aTemplate) {
+ NS_ASSERTION(!mTable.Search(aContent), "aContent already in map");
+
+ auto entry = static_cast<Entry*>(mTable.Add(aContent, fallible));
+
+ if (entry) {
+ entry->mContent = aContent;
+ entry->mTemplate = aTemplate;
+ }
+ }
+
+ void
+ Remove(nsIContent* aContent) {
+ mTable.Remove(aContent);
+
+ for (nsIContent* child = aContent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ Remove(child);
+ }
+ }
+
+
+ void
+ GetTemplateFor(nsIContent* aContent, nsIContent** aResult) {
+ auto entry = static_cast<Entry*>(mTable.Search(aContent));
+ if (entry)
+ NS_IF_ADDREF(*aResult = entry->mTemplate);
+ else
+ *aResult = nullptr;
+ }
+
+ void
+ Clear() { mTable.Clear(); }
+};
+
+#endif // nsTemplateMap_h__
+
diff --git a/dom/xul/templates/nsTemplateMatch.cpp b/dom/xul/templates/nsTemplateMatch.cpp
new file mode 100644
index 000000000..1ba1c9d87
--- /dev/null
+++ b/dom/xul/templates/nsTemplateMatch.cpp
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTemplateMatch.h"
+#include "nsTemplateRule.h"
+
+// static
+void
+nsTemplateMatch::Destroy(nsTemplateMatch*& aMatch, bool aRemoveResult)
+{
+ if (aRemoveResult && aMatch->mResult)
+ aMatch->mResult->HasBeenRemoved();
+ ::delete aMatch;
+ aMatch = nullptr;
+}
+
+nsresult
+nsTemplateMatch::RuleMatched(nsTemplateQuerySet* aQuerySet,
+ nsTemplateRule* aRule,
+ int16_t aRuleIndex,
+ nsIXULTemplateResult* aResult)
+{
+ // assign the rule index, used to indicate that a match is active, and
+ // so the tree builder can get the right action body to generate
+ mRuleIndex = aRuleIndex;
+
+ nsCOMPtr<nsIDOMNode> rulenode;
+ aRule->GetRuleNode(getter_AddRefs(rulenode));
+ if (rulenode)
+ return aResult->RuleMatched(aQuerySet->mCompiledQuery, rulenode);
+
+ return NS_OK;
+}
diff --git a/dom/xul/templates/nsTemplateMatch.h b/dom/xul/templates/nsTemplateMatch.h
new file mode 100644
index 000000000..ded2fe1ab
--- /dev/null
+++ b/dom/xul/templates/nsTemplateMatch.h
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTemplateMatch_h__
+#define nsTemplateMatch_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIContent.h"
+#include "nsIXULTemplateQueryProcessor.h"
+#include "nsIXULTemplateResult.h"
+#include "nsRuleNetwork.h"
+
+/**
+ * A match object, where each match object is associated with one result.
+ * There will be one match list for each unique id generated. However, since
+ * there are multiple querysets and each may generate results with the same
+ * id, they are all chained together in a linked list, ordered in the same
+ * order as the respective <queryset> elements they were generated from.
+ * A match can be identified by the container and id. The id is retrievable
+ * from the result.
+ *
+ * Only one match per container and id pair is active at a time, but which
+ * match is active may change as new results are added or removed. When a
+ * match is active, content is generated for that match.
+ *
+ * Matches are stored and owned by the mMatchToMap hash in the template
+ * builder.
+ */
+
+class nsTemplateRule;
+class nsTemplateQuerySet;
+
+class nsTemplateMatch {
+private:
+ // Hide so that only Create() and Destroy() can be used to
+ // allocate and deallocate from the heap
+ void* operator new(size_t) CPP_THROW_NEW { MOZ_ASSERT(0); return nullptr; }
+ void operator delete(void*, size_t) { MOZ_ASSERT(0); }
+
+public:
+ nsTemplateMatch(uint16_t aQuerySetPriority,
+ nsIXULTemplateResult* aResult,
+ nsIContent* aContainer)
+ : mRuleIndex(-1),
+ mQuerySetPriority(aQuerySetPriority),
+ mContainer(aContainer),
+ mResult(aResult),
+ mNext(nullptr)
+ {
+ MOZ_COUNT_CTOR(nsTemplateMatch);
+ }
+
+ ~nsTemplateMatch()
+ {
+ MOZ_COUNT_DTOR(nsTemplateMatch);
+ }
+
+ static nsTemplateMatch*
+ Create(uint16_t aQuerySetPriority,
+ nsIXULTemplateResult* aResult,
+ nsIContent* aContainer) {
+ return ::new nsTemplateMatch(aQuerySetPriority, aResult, aContainer);
+ }
+
+ static void Destroy(nsTemplateMatch*& aMatch, bool aRemoveResult);
+
+ // return true if the the match is active, and has generated output
+ bool IsActive() {
+ return mRuleIndex >= 0;
+ }
+
+ // indicate that a rule is no longer active, used when a query with a
+ // lower priority has overriden the match
+ void SetInactive() {
+ mRuleIndex = -1;
+ }
+
+ // return matching rule index
+ int16_t RuleIndex() {
+ return mRuleIndex;
+ }
+
+ // return priority of query set
+ uint16_t QuerySetPriority() {
+ return mQuerySetPriority;
+ }
+
+ // return container, not addrefed. May be null.
+ nsIContent* GetContainer() {
+ return mContainer;
+ }
+
+ nsresult RuleMatched(nsTemplateQuerySet* aQuerySet,
+ nsTemplateRule* aRule,
+ int16_t aRuleIndex,
+ nsIXULTemplateResult* aResult);
+
+private:
+
+ /**
+ * The index of the rule that matched, or -1 if the match is not active.
+ */
+ int16_t mRuleIndex;
+
+ /**
+ * The priority of the queryset for this rule
+ */
+ uint16_t mQuerySetPriority;
+
+ /**
+ * The container the content generated for the match is inside.
+ */
+ nsCOMPtr<nsIContent> mContainer;
+
+public:
+
+ /**
+ * The result associated with this match
+ */
+ nsCOMPtr<nsIXULTemplateResult> mResult;
+
+ /**
+ * Matches are stored in a linked list, in priority order. This first
+ * match that has a rule set (mRule) is the active match and generates
+ * content. The next match is owned by the builder, which will delete
+ * template matches when needed.
+ */
+ nsTemplateMatch *mNext;
+
+private:
+
+ nsTemplateMatch(const nsTemplateMatch& aMatch) = delete;
+ void operator=(const nsTemplateMatch& aMatch) = delete;
+};
+
+#endif // nsTemplateMatch_h__
+
diff --git a/dom/xul/templates/nsTemplateRule.cpp b/dom/xul/templates/nsTemplateRule.cpp
new file mode 100644
index 000000000..6d82740e3
--- /dev/null
+++ b/dom/xul/templates/nsTemplateRule.cpp
@@ -0,0 +1,422 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTemplateRule.h"
+#include "nsTemplateMatch.h"
+#include "nsXULContentUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsReadableUtils.h"
+#include "nsICollation.h"
+
+nsTemplateCondition::nsTemplateCondition(nsIAtom* aSourceVariable,
+ const nsAString& aRelation,
+ nsIAtom* aTargetVariable,
+ bool aIgnoreCase,
+ bool aNegate)
+ : mSourceVariable(aSourceVariable),
+ mTargetVariable(aTargetVariable),
+ mIgnoreCase(aIgnoreCase),
+ mNegate(aNegate),
+ mNext(nullptr)
+{
+ SetRelation(aRelation);
+
+ MOZ_COUNT_CTOR(nsTemplateCondition);
+}
+
+nsTemplateCondition::nsTemplateCondition(nsIAtom* aSourceVariable,
+ const nsAString& aRelation,
+ const nsAString& aTargets,
+ bool aIgnoreCase,
+ bool aNegate,
+ bool aIsMultiple)
+ : mSourceVariable(aSourceVariable),
+ mIgnoreCase(aIgnoreCase),
+ mNegate(aNegate),
+ mNext(nullptr)
+{
+ SetRelation(aRelation);
+
+ if (aIsMultiple) {
+ int32_t start = 0, end = 0;
+ while ((end = aTargets.FindChar(',',start)) >= 0) {
+ if (end > start) {
+ mTargetList.AppendElement(Substring(aTargets, start, end - start));
+ }
+ start = end + 1;
+ }
+ if (start < int32_t(aTargets.Length())) {
+ mTargetList.AppendElement(Substring(aTargets, start));
+ }
+ }
+ else {
+ mTargetList.AppendElement(aTargets);
+ }
+
+ MOZ_COUNT_CTOR(nsTemplateCondition);
+}
+
+nsTemplateCondition::nsTemplateCondition(const nsAString& aSource,
+ const nsAString& aRelation,
+ nsIAtom* aTargetVariable,
+ bool aIgnoreCase,
+ bool aNegate)
+ : mSource(aSource),
+ mTargetVariable(aTargetVariable),
+ mIgnoreCase(aIgnoreCase),
+ mNegate(aNegate),
+ mNext(nullptr)
+{
+ SetRelation(aRelation);
+
+ MOZ_COUNT_CTOR(nsTemplateCondition);
+}
+
+void
+nsTemplateCondition::SetRelation(const nsAString& aRelation)
+{
+ if (aRelation.EqualsLiteral("equals") || aRelation.IsEmpty())
+ mRelation = eEquals;
+ else if (aRelation.EqualsLiteral("less"))
+ mRelation = eLess;
+ else if (aRelation.EqualsLiteral("greater"))
+ mRelation = eGreater;
+ else if (aRelation.EqualsLiteral("before"))
+ mRelation = eBefore;
+ else if (aRelation.EqualsLiteral("after"))
+ mRelation = eAfter;
+ else if (aRelation.EqualsLiteral("startswith"))
+ mRelation = eStartswith;
+ else if (aRelation.EqualsLiteral("endswith"))
+ mRelation = eEndswith;
+ else if (aRelation.EqualsLiteral("contains"))
+ mRelation = eContains;
+ else
+ mRelation = eUnknown;
+}
+
+bool
+nsTemplateCondition::CheckMatch(nsIXULTemplateResult* aResult)
+{
+ bool match = false;
+
+ nsAutoString leftString;
+ if (mSourceVariable)
+ aResult->GetBindingFor(mSourceVariable, leftString);
+ else
+ leftString.Assign(mSource);
+
+ if (mTargetVariable) {
+ nsAutoString rightString;
+ aResult->GetBindingFor(mTargetVariable, rightString);
+
+ match = CheckMatchStrings(leftString, rightString);
+ }
+ else {
+ // iterate over the strings in the target and determine
+ // whether there is a match.
+ uint32_t length = mTargetList.Length();
+ for (uint32_t t = 0; t < length; t++) {
+ match = CheckMatchStrings(leftString, mTargetList[t]);
+
+ // stop once a match is found. In negate mode, stop once a
+ // target does not match.
+ if (match != mNegate) break;
+ }
+ }
+
+ return match;
+}
+
+
+bool
+nsTemplateCondition::CheckMatchStrings(const nsAString& aLeftString,
+ const nsAString& aRightString)
+{
+ bool match = false;
+
+ if (aRightString.IsEmpty()) {
+ if ((mRelation == eEquals) && aLeftString.IsEmpty())
+ match = true;
+ }
+ else {
+ switch (mRelation) {
+ case eEquals:
+ if (mIgnoreCase)
+ match = aLeftString.Equals(aRightString,
+ nsCaseInsensitiveStringComparator());
+ else
+ match = aLeftString.Equals(aRightString);
+ break;
+
+ case eLess:
+ case eGreater:
+ {
+ // non-numbers always compare false
+ nsresult err;
+ int32_t leftint = PromiseFlatString(aLeftString).ToInteger(&err);
+ if (NS_SUCCEEDED(err)) {
+ int32_t rightint = PromiseFlatString(aRightString).ToInteger(&err);
+ if (NS_SUCCEEDED(err)) {
+ match = (mRelation == eLess) ? (leftint < rightint) :
+ (leftint > rightint);
+ }
+ }
+
+ break;
+ }
+
+ case eBefore:
+ {
+ nsICollation* collation = nsXULContentUtils::GetCollation();
+ if (collation) {
+ int32_t sortOrder;
+ collation->CompareString((mIgnoreCase ?
+ static_cast<int32_t>(nsICollation::kCollationCaseInSensitive) :
+ static_cast<int32_t>(nsICollation::kCollationCaseSensitive)),
+ aLeftString,
+ aRightString,
+ &sortOrder);
+ match = (sortOrder < 0);
+ }
+ else if (mIgnoreCase) {
+ match = (Compare(aLeftString, aRightString,
+ nsCaseInsensitiveStringComparator()) < 0);
+ }
+ else {
+ match = (Compare(aLeftString, aRightString) < 0);
+ }
+ break;
+ }
+
+ case eAfter:
+ {
+ nsICollation* collation = nsXULContentUtils::GetCollation();
+ if (collation) {
+ int32_t sortOrder;
+ collation->CompareString((mIgnoreCase ?
+ static_cast<int32_t>(nsICollation::kCollationCaseInSensitive) :
+ static_cast<int32_t>(nsICollation::kCollationCaseSensitive)),
+ aLeftString,
+ aRightString,
+ &sortOrder);
+ match = (sortOrder > 0);
+ }
+ else if (mIgnoreCase) {
+ match = (Compare(aLeftString, aRightString,
+ nsCaseInsensitiveStringComparator()) > 0);
+ }
+ else {
+ match = (Compare(aLeftString, aRightString) > 0);
+ }
+ break;
+ }
+
+ case eStartswith:
+ if (mIgnoreCase)
+ match = (StringBeginsWith(aLeftString, aRightString,
+ nsCaseInsensitiveStringComparator()));
+ else
+ match = (StringBeginsWith(aLeftString, aRightString));
+ break;
+
+ case eEndswith:
+ if (mIgnoreCase)
+ match = (StringEndsWith(aLeftString, aRightString,
+ nsCaseInsensitiveStringComparator()));
+ else
+ match = (StringEndsWith(aLeftString, aRightString));
+ break;
+
+ case eContains:
+ {
+ nsAString::const_iterator start, end;
+ aLeftString.BeginReading(start);
+ aLeftString.EndReading(end);
+ if (mIgnoreCase)
+ match = CaseInsensitiveFindInReadable(aRightString, start, end);
+ else
+ match = FindInReadable(aRightString, start, end);
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ if (mNegate) match = !match;
+
+ return match;
+}
+
+nsTemplateRule::nsTemplateRule(nsIContent* aRuleNode,
+ nsIContent* aAction,
+ nsTemplateQuerySet* aQuerySet)
+ : mQuerySet(aQuerySet),
+ mAction(aAction),
+ mBindings(nullptr),
+ mConditions(nullptr)
+{
+ MOZ_COUNT_CTOR(nsTemplateRule);
+ mRuleNode = do_QueryInterface(aRuleNode);
+}
+
+nsTemplateRule::nsTemplateRule(const nsTemplateRule& aOtherRule)
+ : mQuerySet(aOtherRule.mQuerySet),
+ mRuleNode(aOtherRule.mRuleNode),
+ mAction(aOtherRule.mAction),
+ mBindings(nullptr),
+ mConditions(nullptr)
+{
+ MOZ_COUNT_CTOR(nsTemplateRule);
+}
+
+nsTemplateRule::~nsTemplateRule()
+{
+ MOZ_COUNT_DTOR(nsTemplateRule);
+
+ while (mBindings) {
+ Binding* doomed = mBindings;
+ mBindings = mBindings->mNext;
+ delete doomed;
+ }
+
+ while (mConditions) {
+ nsTemplateCondition* cdel = mConditions;
+ mConditions = mConditions->GetNext();
+ delete cdel;
+ }
+}
+
+nsresult
+nsTemplateRule::GetRuleNode(nsIDOMNode** aRuleNode) const
+{
+ *aRuleNode = mRuleNode;
+ NS_IF_ADDREF(*aRuleNode);
+ return NS_OK;
+}
+
+void nsTemplateRule::SetCondition(nsTemplateCondition* aCondition)
+{
+ while (mConditions) {
+ nsTemplateCondition* cdel = mConditions;
+ mConditions = mConditions->GetNext();
+ delete cdel;
+ }
+
+ mConditions = aCondition;
+}
+
+bool
+nsTemplateRule::CheckMatch(nsIXULTemplateResult* aResult) const
+{
+ // check the conditions in the rule first
+ nsTemplateCondition* condition = mConditions;
+ while (condition) {
+ if (!condition->CheckMatch(aResult))
+ return false;
+
+ condition = condition->GetNext();
+ }
+
+ if (mRuleFilter) {
+ // if a rule filter was set, check it for a match. If an error occurs,
+ // assume that the match was acceptable
+ bool match;
+ nsresult rv = mRuleFilter->Match(aResult, mRuleNode, &match);
+ return NS_FAILED(rv) || match;
+ }
+
+ return true;
+}
+
+bool
+nsTemplateRule::HasBinding(nsIAtom* aSourceVariable,
+ nsAString& aExpr,
+ nsIAtom* aTargetVariable) const
+{
+ for (Binding* binding = mBindings; binding != nullptr; binding = binding->mNext) {
+ if ((binding->mSourceVariable == aSourceVariable) &&
+ (binding->mExpr.Equals(aExpr)) &&
+ (binding->mTargetVariable == aTargetVariable))
+ return true;
+ }
+
+ return false;
+}
+
+nsresult
+nsTemplateRule::AddBinding(nsIAtom* aSourceVariable,
+ nsAString& aExpr,
+ nsIAtom* aTargetVariable)
+{
+ NS_PRECONDITION(aSourceVariable != 0, "no source variable!");
+ if (! aSourceVariable)
+ return NS_ERROR_INVALID_ARG;
+
+ NS_PRECONDITION(aTargetVariable != 0, "no target variable!");
+ if (! aTargetVariable)
+ return NS_ERROR_INVALID_ARG;
+
+ NS_ASSERTION(! HasBinding(aSourceVariable, aExpr, aTargetVariable),
+ "binding added twice");
+
+ Binding* newbinding = new Binding;
+ if (! newbinding)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ newbinding->mSourceVariable = aSourceVariable;
+ newbinding->mTargetVariable = aTargetVariable;
+ newbinding->mParent = nullptr;
+
+ newbinding->mExpr.Assign(aExpr);
+
+ Binding* binding = mBindings;
+ Binding** link = &mBindings;
+
+ // Insert it at the end, unless we detect that an existing
+ // binding's source is dependent on the newbinding's target.
+ //
+ // XXXwaterson this isn't enough to make sure that we get all of
+ // the dependencies worked out right, but it'll do for now. For
+ // example, if you have (ab, bc, cd), and insert them in the order
+ // (cd, ab, bc), you'll get (bc, cd, ab). The good news is, if the
+ // person uses a natural ordering when writing the XUL, it'll all
+ // work out ok.
+ while (binding) {
+ if (binding->mSourceVariable == newbinding->mTargetVariable) {
+ binding->mParent = newbinding;
+ break;
+ }
+ else if (binding->mTargetVariable == newbinding->mSourceVariable) {
+ newbinding->mParent = binding;
+ }
+
+ link = &binding->mNext;
+ binding = binding->mNext;
+ }
+
+ // Insert the newbinding
+ *link = newbinding;
+ newbinding->mNext = binding;
+ return NS_OK;
+}
+
+nsresult
+nsTemplateRule::AddBindingsToQueryProcessor(nsIXULTemplateQueryProcessor* aProcessor)
+{
+ Binding* binding = mBindings;
+
+ while (binding) {
+ nsresult rv = aProcessor->AddBinding(mRuleNode, binding->mTargetVariable,
+ binding->mSourceVariable, binding->mExpr);
+ if (NS_FAILED(rv)) return rv;
+
+ binding = binding->mNext;
+ }
+
+ return NS_OK;
+}
diff --git a/dom/xul/templates/nsTemplateRule.h b/dom/xul/templates/nsTemplateRule.h
new file mode 100644
index 000000000..d7821ba29
--- /dev/null
+++ b/dom/xul/templates/nsTemplateRule.h
@@ -0,0 +1,328 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTemplateRule_h__
+#define nsTemplateRule_h__
+
+#include "nsCOMPtr.h"
+#include "nsIAtom.h"
+#include "nsIRDFDataSource.h"
+#include "nsIRDFResource.h"
+#include "nsIContent.h"
+#include "nsIDOMNode.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "nsIXULTemplateRuleFilter.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsIXULTemplateQueryProcessor;
+class nsTemplateQuerySet;
+
+class nsTemplateCondition
+{
+public:
+ // relations that may be used in a rule. They may be negated with the
+ // negate flag. Less and Greater are used for numeric comparisons and
+ // Before and After are used for string comparisons. For Less, Greater,
+ // Before, After, Startswith, Endswith, and Contains, the source is
+ // conceptually on the left of the relation and the target is on the
+ // right. For example, if the relation is Contains, that means Match if
+ // the source contains the target.
+ enum ConditionRelation {
+ eUnknown,
+ eEquals,
+ eLess,
+ eGreater,
+ eBefore,
+ eAfter,
+ eStartswith,
+ eEndswith,
+ eContains
+ };
+
+ nsTemplateCondition(nsIAtom* aSourceVariable,
+ const nsAString& aRelation,
+ nsIAtom* aTargetVariable,
+ bool mIgnoreCase,
+ bool mNegate);
+
+ nsTemplateCondition(nsIAtom* aSourceVariable,
+ const nsAString& aRelation,
+ const nsAString& aTargets,
+ bool mIgnoreCase,
+ bool mNegate,
+ bool aIsMultiple);
+
+ nsTemplateCondition(const nsAString& aSource,
+ const nsAString& aRelation,
+ nsIAtom* aTargetVariable,
+ bool mIgnoreCase,
+ bool mNegate);
+
+ ~nsTemplateCondition() { MOZ_COUNT_DTOR(nsTemplateCondition); }
+
+ nsTemplateCondition* GetNext() { return mNext; }
+ void SetNext(nsTemplateCondition* aNext) { mNext = aNext; }
+
+ void SetRelation(const nsAString& aRelation);
+
+ bool
+ CheckMatch(nsIXULTemplateResult* aResult);
+
+ bool
+ CheckMatchStrings(const nsAString& aLeftString,
+ const nsAString& aRightString);
+protected:
+
+ nsCOMPtr<nsIAtom> mSourceVariable;
+ nsString mSource;
+ ConditionRelation mRelation;
+ nsCOMPtr<nsIAtom> mTargetVariable;
+ nsTArray<nsString> mTargetList;
+ bool mIgnoreCase;
+ bool mNegate;
+
+ nsTemplateCondition* mNext;
+};
+
+/**
+ * A rule consists of:
+ *
+ * - Conditions, a set of unbound variables with consistency
+ * constraints that specify the values that each variable can
+ * assume. The conditions must be completely and consistently
+ * "bound" for the rule to be considered "matched".
+ *
+ * - Bindings, a set of unbound variables with consistency constraints
+ * that specify the values that each variable can assume. Unlike the
+ * conditions, the bindings need not be bound for the rule to be
+ * considered matched.
+ *
+ * - Content that should be constructed when the rule is "activated".
+ *
+ */
+class nsTemplateRule
+{
+public:
+ nsTemplateRule(nsIContent* aRuleNode,
+ nsIContent* aAction,
+ nsTemplateQuerySet* aQuerySet);
+ /**
+ * The copy-constructor should only be called from nsTArray when appending
+ * a new rule, otherwise things break because the copy constructor expects
+ * mBindings and mConditions to be nullptr.
+ */
+ nsTemplateRule(const nsTemplateRule& aOtherRule);
+
+ ~nsTemplateRule();
+
+ /**
+ * Return the <action> node that this rule was constructed from, or its
+ * logical equivalent for shorthand syntaxes. That is, the parent node of
+ * the content that should be generated for this rule.
+ */
+ nsIContent* GetAction() const { return mAction; }
+
+ /**
+ * Return the <rule> content node that this rule was constructed from.
+ * @param aResult an out parameter, which will contain the rule node
+ * @return NS_OK if no errors occur.
+ */
+ nsresult GetRuleNode(nsIDOMNode** aResult) const;
+
+ void SetVars(nsIAtom* aRefVariable, nsIAtom* aMemberVariable)
+ {
+ mRefVariable = aRefVariable;
+ mMemberVariable = aMemberVariable;
+ }
+
+ void SetRuleFilter(nsIXULTemplateRuleFilter* aRuleFilter)
+ {
+ mRuleFilter = aRuleFilter;
+ }
+
+ nsIAtom* GetTag() { return mTag; }
+ void SetTag(nsIAtom* aTag) { mTag = aTag; }
+
+ nsIAtom* GetMemberVariable() { return mMemberVariable; }
+
+ /**
+ * Set the first condition for the rule. Other conditions are linked
+ * to it using the condition's SetNext method.
+ */
+ void SetCondition(nsTemplateCondition* aConditions);
+
+ /**
+ * Check if the result matches the rule by first looking at the conditions.
+ * If the results is accepted by the conditions, the rule filter, if any
+ * was set, is checked. If either check rejects a result, a match cannot
+ * occur for this rule and result.
+ */
+ bool
+ CheckMatch(nsIXULTemplateResult* aResult) const;
+
+ /**
+ * Determine if the rule has the specified binding
+ */
+ bool
+ HasBinding(nsIAtom* aSourceVariable,
+ nsAString& aExpr,
+ nsIAtom* aTargetVariable) const;
+
+ /**
+ * Add a binding to the rule. A binding consists of an already-bound
+ * source variable, and the RDF property that should be tested to
+ * generate a target value. The target value is bound to a target
+ * variable.
+ *
+ * @param aSourceVariable the source variable that will be used in
+ * the RDF query.
+ * @param aExpr the expression that will be used in the query.
+ * @param aTargetVariable the variable whose value will be bound
+ * to the RDF node that is returned when querying the binding
+ * @return NS_OK if no errors occur.
+ */
+ nsresult AddBinding(nsIAtom* aSourceVariable,
+ nsAString& aExpr,
+ nsIAtom* aTargetVariable);
+
+ /**
+ * Inform the query processor of the bindings that are set for a rule.
+ * This should be called after all the bindings for a rule are compiled.
+ */
+ nsresult
+ AddBindingsToQueryProcessor(nsIXULTemplateQueryProcessor* aProcessor);
+
+ void Traverse(nsCycleCollectionTraversalCallback &cb) const
+ {
+ cb.NoteXPCOMChild(mRuleNode);
+ cb.NoteXPCOMChild(mAction);
+ }
+
+protected:
+
+ struct Binding {
+ nsCOMPtr<nsIAtom> mSourceVariable;
+ nsCOMPtr<nsIAtom> mTargetVariable;
+ nsString mExpr;
+ Binding* mNext;
+ Binding* mParent;
+ };
+
+ // backreference to the query set which owns this rule
+ nsTemplateQuerySet* mQuerySet;
+
+ // the <rule> node, or the <template> node if there is no <rule>
+ nsCOMPtr<nsIDOMNode> mRuleNode;
+
+ // the <action> node, or, if there is no <action>, the container node
+ // which contains the content to generate
+ nsCOMPtr<nsIContent> mAction;
+
+ // the rule filter set by the builder's SetRuleFilter function
+ nsCOMPtr<nsIXULTemplateRuleFilter> mRuleFilter;
+
+ // indicates that the rule will only match when generating content
+ // to be inserted into a container with this tag
+ nsCOMPtr<nsIAtom> mTag;
+
+ // linked-list of the bindings for the rule, owned by the rule.
+ Binding* mBindings;
+
+ nsCOMPtr<nsIAtom> mRefVariable;
+ nsCOMPtr<nsIAtom> mMemberVariable;
+
+ nsTemplateCondition* mConditions; // owned by nsTemplateRule
+};
+
+/** nsTemplateQuerySet
+ *
+ * A single <queryset> which holds the query node and the rules for it.
+ * All builders have at least one queryset, which may be created with an
+ * explicit <queryset> tag or implied if the tag is not used.
+ *
+ * These queryset objects are created and owned by the builder in its
+ * mQuerySets array.
+ */
+class nsTemplateQuerySet
+{
+protected:
+ nsTArray<nsTemplateRule> mRules;
+
+ // a number which increments for each successive queryset. It is stored so
+ // it can be used as an optimization when updating results so that it is
+ // known where to insert them into a match.
+ int32_t mPriority;
+
+public:
+
+ // <query> node
+ nsCOMPtr<nsIContent> mQueryNode;
+
+ // compiled opaque query object returned by the query processor's
+ // CompileQuery call
+ nsCOMPtr<nsISupports> mCompiledQuery;
+
+ // indicates that the query will only generate content to be inserted into
+ // a container with this tag
+ nsCOMPtr<nsIAtom> mTag;
+
+ explicit nsTemplateQuerySet(int32_t aPriority)
+ : mPriority(aPriority)
+ {
+ MOZ_COUNT_CTOR(nsTemplateQuerySet);
+ }
+
+ ~nsTemplateQuerySet()
+ {
+ MOZ_COUNT_DTOR(nsTemplateQuerySet);
+ }
+
+ int32_t Priority() const
+ {
+ return mPriority;
+ }
+
+ nsIAtom* GetTag() { return mTag; }
+ void SetTag(nsIAtom* aTag) { mTag = aTag; }
+
+ nsTemplateRule* NewRule(nsIContent* aRuleNode,
+ nsIContent* aAction,
+ nsTemplateQuerySet* aQuerySet)
+ {
+ // nsTemplateMatch stores the index as a 16-bit value,
+ // so check to make sure for overflow
+ if (mRules.Length() == INT16_MAX)
+ return nullptr;
+
+ return mRules.AppendElement(nsTemplateRule(aRuleNode, aAction,
+ aQuerySet));
+ }
+
+ void RemoveRule(nsTemplateRule *aRule)
+ {
+ mRules.RemoveElementAt(aRule - mRules.Elements());
+ }
+
+ int16_t RuleCount() const
+ {
+ return mRules.Length();
+ }
+
+ nsTemplateRule* GetRuleAt(int16_t aIndex)
+ {
+ if (uint32_t(aIndex) < mRules.Length()) {
+ return &mRules[aIndex];
+ }
+ return nullptr;
+ }
+
+ void Clear()
+ {
+ mRules.Clear();
+ }
+};
+
+#endif // nsTemplateRule_h__
diff --git a/dom/xul/templates/nsTreeRows.cpp b/dom/xul/templates/nsTreeRows.cpp
new file mode 100644
index 000000000..b77a97213
--- /dev/null
+++ b/dom/xul/templates/nsTreeRows.cpp
@@ -0,0 +1,482 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsString.h"
+#include "nsTreeRows.h"
+#include <algorithm>
+
+nsTreeRows::Subtree*
+nsTreeRows::EnsureSubtreeFor(Subtree* aParent,
+ int32_t aChildIndex)
+{
+ Subtree* subtree = GetSubtreeFor(aParent, aChildIndex);
+
+ if (! subtree) {
+ subtree = aParent->mRows[aChildIndex].mSubtree = new Subtree(aParent);
+ InvalidateCachedRow();
+ }
+
+ return subtree;
+}
+
+nsTreeRows::Subtree*
+nsTreeRows::GetSubtreeFor(const Subtree* aParent,
+ int32_t aChildIndex,
+ int32_t* aSubtreeSize)
+{
+ NS_PRECONDITION(aParent, "no parent");
+ NS_PRECONDITION(aChildIndex >= 0, "bad child index");
+
+ Subtree* result = nullptr;
+
+ if (aChildIndex < aParent->mCount)
+ result = aParent->mRows[aChildIndex].mSubtree;
+
+ if (aSubtreeSize)
+ *aSubtreeSize = result ? result->mSubtreeSize : 0;
+
+ return result;
+}
+
+void
+nsTreeRows::RemoveSubtreeFor(Subtree* aParent, int32_t aChildIndex)
+{
+ NS_PRECONDITION(aParent, "no parent");
+ NS_PRECONDITION(aChildIndex >= 0 && aChildIndex < aParent->mCount, "bad child index");
+
+ Row& row = aParent->mRows[aChildIndex];
+
+ if (row.mSubtree) {
+ int32_t subtreeSize = row.mSubtree->GetSubtreeSize();
+
+ delete row.mSubtree;
+ row.mSubtree = nullptr;
+
+ for (Subtree* subtree = aParent; subtree != nullptr; subtree = subtree->mParent)
+ subtree->mSubtreeSize -= subtreeSize;
+ }
+
+ InvalidateCachedRow();
+}
+
+nsTreeRows::iterator
+nsTreeRows::First()
+{
+ iterator result;
+ result.Append(&mRoot, 0);
+ result.SetRowIndex(0);
+ return result;
+}
+
+nsTreeRows::iterator
+nsTreeRows::Last()
+{
+ iterator result;
+
+ // Build up a path along the rightmost edge of the tree
+ Subtree* current = &mRoot;
+ int32_t count = current->Count();
+ do {
+ int32_t last = count - 1;
+ result.Append(current, last);
+ current = count ? GetSubtreeFor(current, last) : nullptr;
+ } while (current && ((count = current->Count()) != 0));
+
+ // Now, at the bottom rightmost leaf, advance us one off the end.
+ result.GetTop().mChildIndex++;
+
+ // Our row index will be the size of the root subree, plus one.
+ result.SetRowIndex(mRoot.GetSubtreeSize() + 1);
+
+ return result;
+}
+
+nsTreeRows::iterator
+nsTreeRows::operator[](int32_t aRow)
+{
+ // See if we're just lucky, and end up with something
+ // nearby. (This tends to happen a lot due to the way that we get
+ // asked for rows n' stuff.)
+ int32_t last = mLastRow.GetRowIndex();
+ if (last != -1) {
+ if (aRow == last)
+ return mLastRow;
+ else if (last + 1 == aRow)
+ return ++mLastRow;
+ else if (last - 1 == aRow)
+ return --mLastRow;
+ }
+
+ // Nope. Construct a path to the specified index. This is a little
+ // bit better than O(n), because we can skip over subtrees. (So it
+ // ends up being approximately linear in the subtree size, instead
+ // of the entire view size. But, most of the time, big views are
+ // flat. Oh well.)
+ iterator result;
+ Subtree* current = &mRoot;
+
+ int32_t index = 0;
+ result.SetRowIndex(aRow);
+
+ do {
+ int32_t subtreeSize;
+ Subtree* subtree = GetSubtreeFor(current, index, &subtreeSize);
+
+ if (subtreeSize >= aRow) {
+ result.Append(current, index);
+ current = subtree;
+ index = 0;
+ --aRow;
+ }
+ else {
+ ++index;
+ aRow -= subtreeSize + 1;
+ }
+ } while (aRow >= 0);
+
+ mLastRow = result;
+ return result;
+}
+
+nsTreeRows::iterator
+nsTreeRows::FindByResource(nsIRDFResource* aResource)
+{
+ // XXX Mmm, scan through the rows one-by-one...
+ iterator last = Last();
+ iterator iter;
+
+ nsresult rv;
+ nsAutoString resourceid;
+ bool stringmode = false;
+
+ for (iter = First(); iter != last; ++iter) {
+ if (!stringmode) {
+ nsCOMPtr<nsIRDFResource> findres;
+ rv = iter->mMatch->mResult->GetResource(getter_AddRefs(findres));
+ if (NS_FAILED(rv)) return last;
+
+ if (findres == aResource)
+ break;
+
+ if (! findres) {
+ const char *uri;
+ aResource->GetValueConst(&uri);
+ CopyUTF8toUTF16(uri, resourceid);
+
+ // set stringmode and fall through
+ stringmode = true;
+ }
+ }
+
+ // additional check because previous block could change stringmode
+ if (stringmode) {
+ nsAutoString findid;
+ rv = iter->mMatch->mResult->GetId(findid);
+ if (NS_FAILED(rv)) return last;
+
+ if (resourceid.Equals(findid))
+ break;
+ }
+ }
+
+ return iter;
+}
+
+nsTreeRows::iterator
+nsTreeRows::Find(nsIXULTemplateResult *aResult)
+{
+ // XXX Mmm, scan through the rows one-by-one...
+ iterator last = Last();
+ iterator iter;
+
+ for (iter = First(); iter != last; ++iter) {
+ if (aResult == iter->mMatch->mResult)
+ break;
+ }
+
+ return iter;
+}
+
+void
+nsTreeRows::Clear()
+{
+ mRoot.Clear();
+ InvalidateCachedRow();
+}
+
+//----------------------------------------------------------------------
+//
+// nsTreeRows::Subtree
+//
+
+nsTreeRows::Subtree::~Subtree()
+{
+ Clear();
+}
+
+void
+nsTreeRows::Subtree::Clear()
+{
+ for (int32_t i = mCount - 1; i >= 0; --i)
+ delete mRows[i].mSubtree;
+
+ delete[] mRows;
+
+ mRows = nullptr;
+ mCount = mCapacity = mSubtreeSize = 0;
+}
+
+nsTreeRows::iterator
+nsTreeRows::Subtree::InsertRowAt(nsTemplateMatch* aMatch, int32_t aIndex)
+{
+ if (mCount >= mCapacity || aIndex >= mCapacity) {
+ int32_t newCapacity = std::max(mCapacity * 2, aIndex + 1);
+ Row* newRows = new Row[newCapacity];
+ if (! newRows)
+ return iterator();
+
+ for (int32_t i = mCount - 1; i >= 0; --i)
+ newRows[i] = mRows[i];
+
+ delete[] mRows;
+
+ mRows = newRows;
+ mCapacity = newCapacity;
+ }
+
+ for (int32_t i = mCount - 1; i >= aIndex; --i)
+ mRows[i + 1] = mRows[i];
+
+ mRows[aIndex].mMatch = aMatch;
+ mRows[aIndex].mContainerType = eContainerType_Unknown;
+ mRows[aIndex].mContainerState = eContainerState_Unknown;
+ mRows[aIndex].mContainerFill = eContainerFill_Unknown;
+ mRows[aIndex].mSubtree = nullptr;
+ ++mCount;
+
+ // Now build an iterator that points to the newly inserted element.
+ int32_t rowIndex = 0;
+ iterator result;
+ result.Push(this, aIndex);
+
+ for ( ; --aIndex >= 0; ++rowIndex) {
+ // Account for open subtrees in the absolute row index.
+ const Subtree *subtree = mRows[aIndex].mSubtree;
+ if (subtree)
+ rowIndex += subtree->mSubtreeSize;
+ }
+
+ Subtree *subtree = this;
+ do {
+ // Note that the subtree's size has expanded.
+ ++subtree->mSubtreeSize;
+
+ Subtree *parent = subtree->mParent;
+ if (! parent)
+ break;
+
+ // Account for open subtrees in the absolute row index.
+ int32_t count = parent->Count();
+ for (aIndex = 0; aIndex < count; ++aIndex, ++rowIndex) {
+ const Subtree *child = (*parent)[aIndex].mSubtree;
+ if (subtree == child)
+ break;
+
+ if (child)
+ rowIndex += child->mSubtreeSize;
+ }
+
+ NS_ASSERTION(aIndex < count, "couldn't find subtree in parent");
+
+ result.Push(parent, aIndex);
+ subtree = parent;
+ ++rowIndex; // One for the parent row.
+ } while (1);
+
+ result.SetRowIndex(rowIndex);
+ return result;
+}
+
+void
+nsTreeRows::Subtree::RemoveRowAt(int32_t aIndex)
+{
+ NS_PRECONDITION(aIndex >= 0 && aIndex < Count(), "bad index");
+ if (aIndex < 0 || aIndex >= Count())
+ return;
+
+ // How big is the subtree we're going to be removing?
+ int32_t subtreeSize = mRows[aIndex].mSubtree
+ ? mRows[aIndex].mSubtree->GetSubtreeSize()
+ : 0;
+
+ ++subtreeSize;
+
+ delete mRows[aIndex].mSubtree;
+
+ for (int32_t i = aIndex + 1; i < mCount; ++i)
+ mRows[i - 1] = mRows[i];
+
+ --mCount;
+
+ for (Subtree* subtree = this; subtree != nullptr; subtree = subtree->mParent)
+ subtree->mSubtreeSize -= subtreeSize;
+}
+
+//----------------------------------------------------------------------
+//
+// nsTreeRows::iterator
+//
+
+nsTreeRows::iterator::iterator(const iterator& aIterator)
+ : mRowIndex(aIterator.mRowIndex),
+ mLink(aIterator.mLink)
+{
+}
+
+nsTreeRows::iterator&
+nsTreeRows::iterator::operator=(const iterator& aIterator)
+{
+ mRowIndex = aIterator.mRowIndex;
+ mLink = aIterator.mLink;
+ return *this;
+}
+
+void
+nsTreeRows::iterator::Append(Subtree* aParent, int32_t aChildIndex)
+{
+ Link *link = mLink.AppendElement();
+ if (link) {
+ link->mParent = aParent;
+ link->mChildIndex = aChildIndex;
+ }
+ else
+ NS_ERROR("out of memory");
+}
+
+void
+nsTreeRows::iterator::Push(Subtree *aParent, int32_t aChildIndex)
+{
+ Link *link = mLink.InsertElementAt(0);
+ if (link) {
+ link->mParent = aParent;
+ link->mChildIndex = aChildIndex;
+ }
+ else
+ NS_ERROR("out of memory");
+}
+
+bool
+nsTreeRows::iterator::operator==(const iterator& aIterator) const
+{
+ if (GetDepth() != aIterator.GetDepth())
+ return false;
+
+ if (GetDepth() == 0)
+ return true;
+
+ return GetTop() == aIterator.GetTop();
+}
+
+void
+nsTreeRows::iterator::Next()
+{
+ NS_PRECONDITION(GetDepth() > 0, "cannot increment an uninitialized iterator");
+
+ // Increment the absolute row index
+ ++mRowIndex;
+
+ Link& top = GetTop();
+
+ // Is there a child subtree? If so, descend into the child
+ // subtree.
+ Subtree* subtree = top.GetRow().mSubtree;
+
+ if (subtree && subtree->Count()) {
+ Append(subtree, 0);
+ return;
+ }
+
+ // Have we exhausted the current subtree?
+ if (top.mChildIndex >= top.mParent->Count() - 1) {
+ // Yep. See if we've just iterated path the last element in
+ // the tree, period. Walk back up the stack, looking for any
+ // unfinished subtrees.
+ int32_t unfinished;
+ for (unfinished = GetDepth() - 2; unfinished >= 0; --unfinished) {
+ const Link& link = mLink[unfinished];
+ if (link.mChildIndex < link.mParent->Count() - 1)
+ break;
+ }
+
+ // If there are no unfinished subtrees in the stack, then this
+ // iterator is exhausted. Leave it in the same state that
+ // Last() does.
+ if (unfinished < 0) {
+ top.mChildIndex++;
+ return;
+ }
+
+ // Otherwise, we ran off the end of one of the inner
+ // subtrees. Pop up to the next unfinished level in the stack.
+ mLink.SetLength(unfinished + 1);
+ }
+
+ // Advance to the next child in this subtree
+ ++(GetTop().mChildIndex);
+}
+
+void
+nsTreeRows::iterator::Prev()
+{
+ NS_PRECONDITION(GetDepth() > 0, "cannot increment an uninitialized iterator");
+
+ // Decrement the absolute row index
+ --mRowIndex;
+
+ // Move to the previous child in this subtree
+ --(GetTop().mChildIndex);
+
+ // Have we exhausted the current subtree?
+ if (GetTop().mChildIndex < 0) {
+ // Yep. See if we've just iterated back to the first element
+ // in the tree, period. Walk back up the stack, looking for
+ // any unfinished subtrees.
+ int32_t unfinished;
+ for (unfinished = GetDepth() - 2; unfinished >= 0; --unfinished) {
+ const Link& link = mLink[unfinished];
+ if (link.mChildIndex >= 0)
+ break;
+ }
+
+ // If there are no unfinished subtrees in the stack, then this
+ // iterator is exhausted. Leave it in the same state that
+ // First() does.
+ if (unfinished < 0)
+ return;
+
+ // Otherwise, we ran off the end of one of the inner
+ // subtrees. Pop up to the next unfinished level in the stack.
+ mLink.SetLength(unfinished + 1);
+ return;
+ }
+
+ // Is there a child subtree immediately prior to our current
+ // position? If so, descend into it, grovelling down to the
+ // deepest, rightmost left edge.
+ Subtree* parent = GetTop().GetParent();
+ int32_t index = GetTop().GetChildIndex();
+
+ Subtree* subtree = (*parent)[index].mSubtree;
+
+ if (subtree && subtree->Count()) {
+ do {
+ index = subtree->Count() - 1;
+ Append(subtree, index);
+
+ parent = subtree;
+ subtree = (*parent)[index].mSubtree;
+ } while (subtree && subtree->Count());
+ }
+}
diff --git a/dom/xul/templates/nsTreeRows.h b/dom/xul/templates/nsTreeRows.h
new file mode 100644
index 000000000..801af0226
--- /dev/null
+++ b/dom/xul/templates/nsTreeRows.h
@@ -0,0 +1,437 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsTreeRows_h__
+#define nsTreeRows_h__
+
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "PLDHashTable.h"
+#include "nsIXULTemplateResult.h"
+#include "nsTemplateMatch.h"
+#include "nsIRDFResource.h"
+
+
+/**
+ * This class maintains the state of the XUL tree builder's
+ * rows. It maps a row number to the nsTemplateMatch object that
+ * populates the row.
+ */
+class nsTreeRows
+{
+public:
+ class iterator;
+ friend class iterator;
+
+ enum Direction { eDirection_Forwards = +1, eDirection_Backwards = -1 };
+
+ enum ContainerType {
+ eContainerType_Unknown = 0,
+ eContainerType_Noncontainer = 1,
+ eContainerType_Container = 2
+ };
+
+ enum ContainerState {
+ eContainerState_Unknown = 0,
+ eContainerState_Open = 1,
+ eContainerState_Closed = 2
+ };
+
+ enum ContainerFill {
+ eContainerFill_Unknown = 0,
+ eContainerFill_Empty = 1,
+ eContainerFill_Nonempty = 2
+ };
+
+ class Subtree;
+
+ /**
+ * A row in the tree. Contains the match that the row
+ * corresponds to, and a pointer to the row's subtree, if there
+ * are any.
+ */
+ struct Row {
+ nsTemplateMatch* mMatch;
+ ContainerType mContainerType : 4;
+ ContainerState mContainerState : 4;
+ ContainerFill mContainerFill : 4;
+
+ Subtree* mSubtree; // XXX eventually move to hashtable
+ };
+
+ /**
+ * A subtree in the tree. A subtree contains rows, which may
+ * contain other subtrees.
+ */
+ class Subtree {
+ protected:
+ friend class nsTreeRows; // so that it can access members, for now
+
+ /**
+ * The parent subtree; null if we're the root
+ */
+ Subtree* mParent;
+
+ /**
+ * The number of immediate children in this subtree
+ */
+ int32_t mCount;
+
+ /**
+ * The capacity of the subtree
+ */
+ int32_t mCapacity;
+
+ /**
+ * The total number of rows in this subtree, recursively
+ * including child subtrees.
+ */
+ int32_t mSubtreeSize;
+
+ /**
+ * The array of rows in the subtree
+ */
+ Row* mRows;
+
+ public:
+ /**
+ * Creates a subtree with the specified parent.
+ */
+ explicit Subtree(Subtree* aParent)
+ : mParent(aParent),
+ mCount(0),
+ mCapacity(0),
+ mSubtreeSize(0),
+ mRows(nullptr) {}
+
+ ~Subtree();
+
+ /**
+ * Return the number of immediate child rows in the subtree
+ */
+ int32_t Count() const { return mCount; }
+
+ /**
+ * Return the number of rows in this subtree, as well as all
+ * the subtrees it contains.
+ */
+ int32_t GetSubtreeSize() const { return mSubtreeSize; }
+
+ /**
+ * Retrieve the immediate child row at the specified index.
+ */
+ const Row& operator[](int32_t aIndex) const {
+ NS_PRECONDITION(aIndex >= 0 && aIndex < mCount, "bad index");
+ return mRows[aIndex]; }
+
+ /**
+ * Retrieve the immediate row at the specified index.
+ */
+ Row& operator[](int32_t aIndex) {
+ NS_PRECONDITION(aIndex >= 0 && aIndex < mCount, "bad index");
+ return mRows[aIndex]; }
+
+ /**
+ * Remove all rows from the subtree.
+ */
+ void Clear();
+
+ protected:
+ /**
+ * Insert an immediate child row at the specified index.
+ */
+ iterator InsertRowAt(nsTemplateMatch* aMatch, int32_t aIndex);
+
+ /**
+ * Remove an immediate child row from the specified index.
+ */
+ void RemoveRowAt(int32_t aChildIndex);
+ };
+
+ friend class Subtree;
+
+protected:
+ /**
+ * A link in the path through the view's tree.
+ */
+ struct Link {
+ Subtree* mParent;
+ int32_t mChildIndex;
+
+ Link&
+ operator=(const Link& aLink) {
+ mParent = aLink.mParent;
+ mChildIndex = aLink.mChildIndex;
+ return *this; }
+
+ bool
+ operator==(const Link& aLink) const {
+ return (mParent == aLink.mParent)
+ && (mChildIndex == aLink.mChildIndex); }
+
+ Subtree* GetParent() { return mParent; }
+ const Subtree* GetParent() const { return mParent; }
+
+ int32_t GetChildIndex() const { return mChildIndex; }
+
+ Row& GetRow() { return (*mParent)[mChildIndex]; }
+ const Row& GetRow() const { return (*mParent)[mChildIndex]; }
+ };
+
+public:
+ /**
+ * An iterator that can be used to traverse the tree view.
+ */
+ class iterator {
+ protected:
+ int32_t mRowIndex;
+ AutoTArray<Link, 8> mLink;
+
+ void Next();
+ void Prev();
+
+ friend class Subtree; // so InsertRowAt can initialize us
+ friend class nsTreeRows; // so nsTreeRows can initialize us
+
+ /**
+ * Used by operator[]() to initialize an iterator.
+ */
+ void Append(Subtree* aParent, int32_t aChildIndex);
+
+ /**
+ * Used by InsertRowAt() to initialize an iterator.
+ */
+ void Push(Subtree *aParent, int32_t aChildIndex);
+
+ /**
+ * Used by operator[]() and InsertRowAt() to initialize an iterator.
+ */
+ void SetRowIndex(int32_t aRowIndex) { mRowIndex = aRowIndex; }
+
+ /**
+ * Handy accessors to the top element.
+ */
+ Link& GetTop() { return mLink[mLink.Length() - 1]; }
+ const Link& GetTop() const { return mLink[mLink.Length() - 1]; }
+
+ public:
+ iterator() : mRowIndex(-1) {}
+
+ iterator(const iterator& aIterator);
+ iterator& operator=(const iterator& aIterator);
+
+ bool operator==(const iterator& aIterator) const;
+
+ bool operator!=(const iterator& aIterator) const {
+ return !aIterator.operator==(*this); }
+
+ const Row& operator*() const { return GetTop().GetRow(); }
+ Row& operator*() { return GetTop().GetRow(); }
+
+ const Row* operator->() const { return &(GetTop().GetRow()); }
+ Row* operator->() { return &(GetTop().GetRow()); }
+
+ iterator& operator++() { Next(); return *this; }
+ iterator operator++(int) { iterator temp(*this); Next(); return temp; }
+ iterator& operator--() { Prev(); return *this; }
+ iterator operator--(int) { iterator temp(*this); Prev(); return temp; }
+
+ /**
+ * Return the current parent link
+ */
+ Subtree* GetParent() { return GetTop().GetParent(); }
+
+ const Subtree* GetParent() const { return GetTop().GetParent(); }
+
+ /**
+ * Return the current child index
+ */
+ int32_t GetChildIndex() const { return GetTop().GetChildIndex(); }
+
+ /**
+ * Return the depth of the path the iterator is maintaining
+ * into the tree.
+ */
+ int32_t GetDepth() const { return mLink.Length(); }
+
+ /**
+ * Return the current row index of the iterator
+ */
+ int32_t GetRowIndex() const { return mRowIndex; }
+
+ /**
+ * Pop the iterator up a level.
+ */
+ iterator& Pop() { mLink.SetLength(GetDepth() - 1); return *this; }
+ };
+
+ /**
+ * Retrieve the first element in the view
+ */
+ iterator First();
+
+ /**
+ * Retrieve (one past) the last element in the view
+ */
+ iterator Last();
+
+ /**
+ * Find the row that contains the given resource
+ */
+ iterator FindByResource(nsIRDFResource* aResource);
+
+ /**
+ * Find the row that contains the result
+ */
+ iterator Find(nsIXULTemplateResult* aResult);
+
+ /**
+ * Retrieve the ith element in the view
+ */
+ iterator operator[](int32_t aIndex);
+
+ nsTreeRows() : mRoot(nullptr) {}
+ ~nsTreeRows() {}
+
+ /**
+ * Ensure that a child subtree exists within the specified parent
+ * at the specified child index within the parent. (In other
+ * words, create a subtree if one doesn't already exist.)
+ */
+ Subtree*
+ EnsureSubtreeFor(Subtree* aParent, int32_t aChildIndex);
+
+ /**
+ * Ensure that a child subtree exists at the iterator's position.
+ */
+ Subtree*
+ EnsureSubtreeFor(iterator& aIterator) {
+ return EnsureSubtreeFor(aIterator.GetParent(),
+ aIterator.GetChildIndex()); }
+
+ /**
+ * Get the child subtree for the specified parent at the specified
+ * child index. Optionally return the child subtree's size. Will
+ * return `null' if no subtree exists.
+ */
+ Subtree*
+ GetSubtreeFor(const Subtree* aParent,
+ int32_t aChildIndex,
+ int32_t* aSubtreeSize = nullptr);
+
+ /**
+ * Retrieve the size of the subtree within the specified parent.
+ */
+ int32_t
+ GetSubtreeSizeFor(const Subtree* aParent,
+ int32_t aChildIndex) {
+ int32_t size;
+ GetSubtreeFor(aParent, aChildIndex, &size);
+ return size; }
+
+ /**
+ * Retrieve the size of the subtree within the specified parent.
+ */
+ int32_t
+ GetSubtreeSizeFor(const iterator& aIterator) {
+ int32_t size;
+ GetSubtreeFor(aIterator.GetParent(), aIterator.GetChildIndex(), &size);
+ return size; }
+
+ /**
+ * Remove the specified subtree for a row, leaving the row itself
+ * intact.
+ */
+ void
+ RemoveSubtreeFor(Subtree* aParent, int32_t aChildIndex);
+
+ /**
+ * Remove the specified subtree for a row, leaving the row itself
+ * intact.
+ */
+ void
+ RemoveSubtreeFor(iterator& aIterator) {
+ RemoveSubtreeFor(aIterator.GetParent(), aIterator.GetChildIndex()); }
+
+ /**
+ * Remove the specified row from the view
+ */
+ int32_t
+ RemoveRowAt(iterator& aIterator) {
+ iterator temp = aIterator--;
+ Subtree* parent = temp.GetParent();
+ parent->RemoveRowAt(temp.GetChildIndex());
+ InvalidateCachedRow();
+ return parent->Count(); }
+
+ /**
+ * Insert a new match into the view
+ */
+ iterator
+ InsertRowAt(nsTemplateMatch* aMatch, Subtree* aSubtree, int32_t aChildIndex) {
+ InvalidateCachedRow();
+ return aSubtree->InsertRowAt(aMatch, aChildIndex); }
+
+ /**
+ * Raw access to the rows; e.g., for sorting.
+ */
+ Row*
+ GetRowsFor(Subtree* aSubtree) { return aSubtree->mRows; }
+
+ /**
+ * Remove all of the rows
+ */
+ void Clear();
+
+ /**
+ * Return the total number of rows in the tree view.
+ */
+ int32_t Count() const { return mRoot.GetSubtreeSize(); }
+
+ /**
+ * Retrieve the root subtree
+ */
+ Subtree* GetRoot() { return &mRoot; }
+
+ /**
+ * Set the root resource for the view
+ */
+ void SetRootResource(nsIRDFResource* aResource) {
+ mRootResource = aResource; }
+
+ /**
+ * Retrieve the root resource for the view
+ */
+ nsIRDFResource* GetRootResource() {
+ return mRootResource.get(); }
+
+ /**
+ * Invalidate the cached row; e.g., because the view has changed
+ * in a way that would corrupt the iterator.
+ */
+ void
+ InvalidateCachedRow() { mLastRow = iterator(); }
+
+protected:
+ /**
+ * The root subtree.
+ */
+ Subtree mRoot;
+
+ /**
+ * The root resource for the view
+ */
+ nsCOMPtr<nsIRDFResource> mRootResource;
+
+ /**
+ * The last row that was asked for by operator[]. By remembering
+ * this, we can usually avoid the O(n) search through the row
+ * array to find the row at the specified index.
+ */
+ iterator mLastRow;
+};
+
+
+#endif // nsTreeRows_h__
diff --git a/dom/xul/templates/nsXMLBinding.cpp b/dom/xul/templates/nsXMLBinding.cpp
new file mode 100644
index 000000000..9c1965ce2
--- /dev/null
+++ b/dom/xul/templates/nsXMLBinding.cpp
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsXULTemplateQueryProcessorXML.h"
+#include "nsXULTemplateResultXML.h"
+#include "nsXMLBinding.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/XPathResult.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsXMLBindingSet::~nsXMLBindingSet()
+{}
+
+void
+nsXMLBindingSet::AddBinding(nsIAtom* aVar, nsAutoPtr<XPathExpression>&& aExpr)
+{
+ nsAutoPtr<nsXMLBinding> newbinding(new nsXMLBinding(aVar, Move(aExpr)));
+
+ if (mFirst) {
+ nsXMLBinding* binding = mFirst;
+
+ while (binding) {
+ // if the target variable is already used in a binding, ignore it
+ // since it won't be useful for anything
+ if (binding->mVar == aVar)
+ return;
+
+ // add the binding at the end of the list
+ if (!binding->mNext) {
+ binding->mNext = newbinding;
+ return;
+ }
+
+ binding = binding->mNext;
+ }
+ }
+ else {
+ mFirst = newbinding;
+ }
+}
+
+int32_t
+nsXMLBindingSet::LookupTargetIndex(nsIAtom* aTargetVariable,
+ nsXMLBinding** aBinding)
+{
+ int32_t idx = 0;
+ nsXMLBinding* binding = mFirst;
+
+ while (binding) {
+ if (binding->mVar == aTargetVariable) {
+ *aBinding = binding;
+ return idx;
+ }
+ idx++;
+ binding = binding->mNext;
+ }
+
+ *aBinding = nullptr;
+ return -1;
+}
+
+XPathResult*
+nsXMLBindingValues::GetAssignmentFor(nsXULTemplateResultXML* aResult,
+ nsXMLBinding* aBinding,
+ int32_t aIndex,
+ uint16_t aType)
+{
+ XPathResult* value = mValues.SafeElementAt(aIndex);
+ if (value) {
+ return value;
+ }
+
+ nsINode* contextNode = aResult->Node();
+ if (!contextNode) {
+ return nullptr;
+ }
+
+ mValues.EnsureLengthAtLeast(aIndex + 1);
+
+ ErrorResult ignored;
+ mValues[aIndex] = aBinding->mExpr->Evaluate(*contextNode, aType, nullptr,
+ ignored);
+
+ return mValues[aIndex];
+}
+
+nsINode*
+nsXMLBindingValues::GetNodeAssignmentFor(nsXULTemplateResultXML* aResult,
+ nsXMLBinding* aBinding,
+ int32_t aIndex)
+{
+ XPathResult* result = GetAssignmentFor(aResult, aBinding, aIndex,
+ XPathResult::FIRST_ORDERED_NODE_TYPE);
+
+ ErrorResult rv;
+ return result ? result->GetSingleNodeValue(rv) : nullptr;
+}
+
+void
+nsXMLBindingValues::GetStringAssignmentFor(nsXULTemplateResultXML* aResult,
+ nsXMLBinding* aBinding,
+ int32_t aIndex,
+ nsAString& aValue)
+{
+ XPathResult* result = GetAssignmentFor(aResult, aBinding, aIndex,
+ XPathResult::STRING_TYPE);
+
+ if (result) {
+ ErrorResult rv;
+ result->GetStringValue(aValue, rv);
+ } else {
+ aValue.Truncate();
+ }
+}
diff --git a/dom/xul/templates/nsXMLBinding.h b/dom/xul/templates/nsXMLBinding.h
new file mode 100644
index 000000000..e72813598
--- /dev/null
+++ b/dom/xul/templates/nsXMLBinding.h
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXMLBinding_h__
+#define nsXMLBinding_h__
+
+#include "nsAutoPtr.h"
+#include "nsIAtom.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/XPathExpression.h"
+
+class nsINode;
+class nsXULTemplateResultXML;
+class nsXMLBindingValues;
+namespace mozilla {
+namespace dom {
+class XPathResult;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * Classes related to storing bindings for XML handling.
+ */
+
+/**
+ * a <binding> description
+ */
+struct nsXMLBinding {
+ nsCOMPtr<nsIAtom> mVar;
+ nsAutoPtr<mozilla::dom::XPathExpression> mExpr;
+
+ nsAutoPtr<nsXMLBinding> mNext;
+
+ nsXMLBinding(nsIAtom* aVar, nsAutoPtr<mozilla::dom::XPathExpression>&& aExpr)
+ : mVar(aVar), mExpr(aExpr), mNext(nullptr)
+ {
+ MOZ_COUNT_CTOR(nsXMLBinding);
+ }
+
+ ~nsXMLBinding()
+ {
+ MOZ_COUNT_DTOR(nsXMLBinding);
+ }
+};
+
+/**
+ * a collection of <binding> descriptors. This object is refcounted by
+ * nsXMLBindingValues objects and the query processor.
+ */
+class nsXMLBindingSet final
+{
+ ~nsXMLBindingSet();
+
+public:
+ // pointer to the first binding in a linked list
+ nsAutoPtr<nsXMLBinding> mFirst;
+
+ NS_INLINE_DECL_REFCOUNTING(nsXMLBindingSet);
+
+ /**
+ * Add a binding to the set
+ */
+ void
+ AddBinding(nsIAtom* aVar, nsAutoPtr<mozilla::dom::XPathExpression>&& aExpr);
+
+ /**
+ * The nsXMLBindingValues class stores an array of values, one for each
+ * target symbol that could be set by the bindings in the set.
+ * LookupTargetIndex determines the index into the array for a given
+ * target symbol.
+ */
+ int32_t
+ LookupTargetIndex(nsIAtom* aTargetVariable, nsXMLBinding** aBinding);
+};
+
+/**
+ * a set of values of bindings. This object is used once per result.
+ */
+class nsXMLBindingValues
+{
+protected:
+
+ // the binding set
+ RefPtr<nsXMLBindingSet> mBindings;
+
+ /**
+ * A set of values for variable bindings. To look up a binding value,
+ * scan through the binding set in mBindings for the right target atom.
+ * Its index will correspond to the index in this array.
+ */
+ nsTArray<RefPtr<mozilla::dom::XPathResult> > mValues;
+
+public:
+
+ nsXMLBindingValues() { MOZ_COUNT_CTOR(nsXMLBindingValues); }
+ ~nsXMLBindingValues() { MOZ_COUNT_DTOR(nsXMLBindingValues); }
+
+ nsXMLBindingSet* GetBindingSet() { return mBindings; }
+
+ void SetBindingSet(nsXMLBindingSet* aBindings) { mBindings = aBindings; }
+
+ int32_t
+ LookupTargetIndex(nsIAtom* aTargetVariable, nsXMLBinding** aBinding)
+ {
+ return mBindings ?
+ mBindings->LookupTargetIndex(aTargetVariable, aBinding) : -1;
+ }
+
+ /**
+ * Retrieve the assignment for a particular variable
+ *
+ * aResult the result generated from the template
+ * aBinding the binding looked up using LookupTargetIndex
+ * aIndex the index of the assignment to retrieve
+ * aType the type of result expected
+ */
+ mozilla::dom::XPathResult*
+ GetAssignmentFor(nsXULTemplateResultXML* aResult,
+ nsXMLBinding* aBinding,
+ int32_t idx,
+ uint16_t type);
+
+ nsINode*
+ GetNodeAssignmentFor(nsXULTemplateResultXML* aResult,
+ nsXMLBinding* aBinding,
+ int32_t idx);
+
+ void
+ GetStringAssignmentFor(nsXULTemplateResultXML* aResult,
+ nsXMLBinding* aBinding,
+ int32_t idx,
+ nsAString& aValue);
+};
+
+#endif // nsXMLBinding_h__
diff --git a/dom/xul/templates/nsXULContentBuilder.cpp b/dom/xul/templates/nsXULContentBuilder.cpp
new file mode 100644
index 000000000..71c285cc4
--- /dev/null
+++ b/dom/xul/templates/nsXULContentBuilder.cpp
@@ -0,0 +1,1976 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsContentCID.h"
+#include "nsIDocument.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMXULDocument.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsIServiceManager.h"
+#include "nsIXULDocument.h"
+
+#include "nsContentSupportMap.h"
+#include "nsRDFConMemberTestNode.h"
+#include "nsRDFPropertyTestNode.h"
+#include "nsXULSortService.h"
+#include "nsTemplateRule.h"
+#include "nsTemplateMap.h"
+#include "nsTArray.h"
+#include "nsXPIDLString.h"
+#include "nsGkAtoms.h"
+#include "nsXULContentUtils.h"
+#include "nsXULElement.h"
+#include "nsXULTemplateBuilder.h"
+#include "nsNodeInfoManager.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentUtils.h"
+#include "nsAttrName.h"
+#include "nsNodeUtils.h"
+#include "mozAutoDocUpdate.h"
+#include "nsTextNode.h"
+#include "mozilla/dom/Element.h"
+
+#include "PLDHashTable.h"
+#include "rdf.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+//----------------------------------------------------------------------
+//
+// Return values for EnsureElementHasGenericChild()
+//
+#define NS_ELEMENT_GOT_CREATED NS_RDF_NO_VALUE
+#define NS_ELEMENT_WAS_THERE NS_OK
+
+//----------------------------------------------------------------------
+//
+// nsXULContentBuilder
+//
+
+/**
+ * The content builder generates DOM nodes from a template. The actual content
+ * generation is done entirely inside BuildContentFromTemplate.
+ *
+ * Content generation is centered around the generation node (the node with
+ * uri="?member" on it). Nodes above the generation node are unique and
+ * generated only once. BuildContentFromTemplate will be passed the unique
+ * flag as an argument for content at this point and will recurse until it
+ * finds the generation node.
+ *
+ * Once the generation node has been found, the results for that content node
+ * are added to the content map, stored in mContentSupportMap.
+ *
+ * If recursion is allowed, generation continues, where the generation node
+ * becomes the container to insert into.
+ */
+class nsXULContentBuilder : public nsXULTemplateBuilder
+{
+public:
+ // nsIXULTemplateBuilder interface
+ NS_IMETHOD CreateContents(nsIContent* aElement, bool aForceCreation) override;
+
+ NS_IMETHOD HasGeneratedContent(nsIRDFResource* aResource,
+ nsIAtom* aTag,
+ bool* aGenerated) override;
+
+ NS_IMETHOD GetResultForContent(nsIDOMElement* aContent,
+ nsIXULTemplateResult** aResult) override;
+
+ // nsIMutationObserver interface
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+protected:
+ friend nsresult
+ NS_NewXULContentBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+ nsXULContentBuilder();
+
+ void Traverse(nsCycleCollectionTraversalCallback& aCb) const override
+ {
+ mSortState.Traverse(aCb);
+ }
+
+ virtual void Uninit(bool aIsFinal) override;
+
+ // Implementation methods
+ nsresult
+ OpenContainer(nsIContent* aElement);
+
+ nsresult
+ CloseContainer(nsIContent* aElement);
+
+ /**
+ * Build content from a template for a given result. This will be called
+ * recursively or on demand and will be called for every node in the
+ * generated content tree.
+ */
+ nsresult
+ BuildContentFromTemplate(nsIContent *aTemplateNode,
+ nsIContent *aResourceNode,
+ nsIContent *aRealNode,
+ bool aIsUnique,
+ bool aIsSelfReference,
+ nsIXULTemplateResult* aChild,
+ bool aNotify,
+ nsTemplateMatch* aMatch,
+ nsIContent** aContainer,
+ int32_t* aNewIndexInContainer);
+
+ /**
+ * Copy the attributes from the template node to the node generated
+ * from it, performing any substitutions.
+ *
+ * @param aTemplateNode node within template
+ * @param aRealNode generated node to set attibutes upon
+ * @param aResult result to look up variable->value bindings in
+ * @param aNotify true to notify of DOM changes
+ */
+ nsresult
+ CopyAttributesToElement(nsIContent* aTemplateNode,
+ nsIContent* aRealNode,
+ nsIXULTemplateResult* aResult,
+ bool aNotify);
+
+ /**
+ * Add any necessary persistent attributes (persist="...") from the
+ * local store to a generated node.
+ *
+ * @param aTemplateNode node within template
+ * @param aRealNode generated node to set persisted attibutes upon
+ * @param aResult result to look up variable->value bindings in
+ */
+ nsresult
+ AddPersistentAttributes(Element* aTemplateNode,
+ nsIXULTemplateResult* aResult,
+ nsIContent* aRealNode);
+
+ /**
+ * Recalculate any attributes that have variable references. This will
+ * be called when a binding has been changed to update the attributes.
+ * The attributes are copied from the node aTemplateNode in the template
+ * to the generated node aRealNode, using the values from the result
+ * aResult. This method will operate recursively.
+ *
+ * @param aTemplateNode node within template
+ * @param aRealNode generated node to set attibutes upon
+ * @param aResult result to look up variable->value bindings in
+ */
+ nsresult
+ SynchronizeUsingTemplate(nsIContent *aTemplateNode,
+ nsIContent* aRealNode,
+ nsIXULTemplateResult* aResult);
+
+ /**
+ * Remove the generated node aContent from the DOM and the hashtables
+ * used by the content builder.
+ */
+ nsresult
+ RemoveMember(nsIContent* aContent);
+
+ /**
+ * Create the appropriate generated content for aElement, by calling
+ * CreateContainerContents.
+ *
+ * @param aElement element to generate content inside
+ * @param aForceCreation true to force creation for closed items such as menus
+ */
+ nsresult
+ CreateTemplateAndContainerContents(nsIContent* aElement,
+ bool aForceCreation);
+
+ /**
+ * Generate the results for a template, by calling
+ * CreateContainerContentsForQuerySet for each queryset.
+ *
+ * @param aElement element to generate content inside
+ * @param aResult reference point for query
+ * @param aForceCreation true to force creation for closed items such as menus
+ * @param aNotify true to notify of DOM changes as each element is inserted
+ * @param aNotifyAtEnd notify at the end of all DOM changes
+ */
+ nsresult
+ CreateContainerContents(nsIContent* aElement,
+ nsIXULTemplateResult* aResult,
+ bool aForceCreation,
+ bool aNotify,
+ bool aNotifyAtEnd);
+
+ /**
+ * Generate the results for a query.
+ *
+ * @param aElement element to generate content inside
+ * @param aResult reference point for query
+ * @param aNotify true to notify of DOM changes
+ * @param aContainer container content was added inside
+ * @param aNewIndexInContainer index with container in which content was added
+ */
+ nsresult
+ CreateContainerContentsForQuerySet(nsIContent* aElement,
+ nsIXULTemplateResult* aResult,
+ bool aNotify,
+ nsTemplateQuerySet* aQuerySet,
+ nsIContent** aContainer,
+ int32_t* aNewIndexInContainer);
+
+ /**
+ * Check if an element with a particular tag exists with a container.
+ * If it is not present, append a new element with that tag into the
+ * container.
+ *
+ * @param aParent parent container
+ * @param aNameSpaceID namespace of tag to locate or create
+ * @param aTag tag to locate or create
+ * @param aNotify true to notify of DOM changes
+ * @param aResult set to the found or created node.
+ */
+ nsresult
+ EnsureElementHasGenericChild(nsIContent* aParent,
+ int32_t aNameSpaceID,
+ nsIAtom* aTag,
+ bool aNotify,
+ nsIContent** aResult);
+
+ bool
+ IsOpen(nsIContent* aElement);
+
+ nsresult
+ RemoveGeneratedContent(nsIContent* aElement);
+
+ nsresult
+ GetElementsForResult(nsIXULTemplateResult* aResult,
+ nsCOMArray<nsIContent>& aElements);
+
+ nsresult
+ CreateElement(int32_t aNameSpaceID,
+ nsIAtom* aTag,
+ Element** aResult);
+
+ /**
+ * Set the container and empty attributes on a node. If
+ * aIgnoreNonContainers is true, then the element is not changed
+ * for non-containers. Otherwise, the container attribute will be set to
+ * false.
+ *
+ * @param aElement element to set attributes on
+ * @param aResult result to use to determine state of attributes
+ * @param aIgnoreNonContainers true to not change for non-containers
+ * @param aNotify true to notify of DOM changes
+ */
+ nsresult
+ SetContainerAttrs(nsIContent *aElement,
+ nsIXULTemplateResult* aResult,
+ bool aIgnoreNonContainers,
+ bool aNotify);
+
+ virtual nsresult
+ RebuildAll() override;
+
+ // GetInsertionLocations, ReplaceMatch and SynchronizeResult are inherited
+ // from nsXULTemplateBuilder
+
+ /**
+ * Return true if the result can be inserted into the template as
+ * generated content. For the content builder, aLocations will be set
+ * to the list of containers where the content should be inserted.
+ */
+ virtual bool
+ GetInsertionLocations(nsIXULTemplateResult* aOldResult,
+ nsCOMArray<nsIContent>** aLocations) override;
+
+ /**
+ * Remove the content associated with aOldResult which no longer matches,
+ * and/or generate content for a new match.
+ */
+ virtual nsresult
+ ReplaceMatch(nsIXULTemplateResult* aOldResult,
+ nsTemplateMatch* aNewMatch,
+ nsTemplateRule* aNewMatchRule,
+ void *aContext) override;
+
+ /**
+ * Synchronize a result bindings with the generated content for that
+ * result. This will be called as a result of the template builder's
+ * ResultBindingChanged method.
+ */
+ virtual nsresult
+ SynchronizeResult(nsIXULTemplateResult* aResult) override;
+
+ /**
+ * Compare a result to a content node. If the generated content for the
+ * result should come before aContent, set aSortOrder to -1. If it should
+ * come after, set sortOrder to 1. If both are equal, set to 0.
+ */
+ nsresult
+ CompareResultToNode(nsIXULTemplateResult* aResult, nsIContent* aContent,
+ int32_t* aSortOrder);
+
+ /**
+ * Insert a generated node into the container where it should go according
+ * to the current sort. aNode is the generated content node and aResult is
+ * the result for the generated node.
+ */
+ nsresult
+ InsertSortedNode(nsIContent* aContainer,
+ nsIContent* aNode,
+ nsIXULTemplateResult* aResult,
+ bool aNotify);
+
+ /**
+ * Maintains a mapping between elements in the DOM and the matches
+ * that they support.
+ */
+ nsContentSupportMap mContentSupportMap;
+
+ /**
+ * Maintains a mapping from an element in the DOM to the template
+ * element that it was created from.
+ */
+ nsTemplateMap mTemplateMap;
+
+ /**
+ * Information about the currently active sort
+ */
+ nsSortState mSortState;
+};
+
+nsresult
+NS_NewXULContentBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult)
+{
+ NS_PRECONDITION(aOuter == nullptr, "no aggregation");
+ if (aOuter)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsresult rv;
+ nsXULContentBuilder* result = new nsXULContentBuilder();
+ NS_ADDREF(result); // stabilize
+
+ rv = result->InitGlobals();
+
+ if (NS_SUCCEEDED(rv))
+ rv = result->QueryInterface(aIID, aResult);
+
+ NS_RELEASE(result);
+ return rv;
+}
+
+nsXULContentBuilder::nsXULContentBuilder()
+{
+ mSortState.initialized = false;
+}
+
+void
+nsXULContentBuilder::Uninit(bool aIsFinal)
+{
+ if (! aIsFinal && mRoot) {
+ nsresult rv = RemoveGeneratedContent(mRoot);
+ if (NS_FAILED(rv))
+ return;
+ }
+
+ // Nuke the content support map completely.
+ mContentSupportMap.Clear();
+ mTemplateMap.Clear();
+
+ mSortState.initialized = false;
+
+ nsXULTemplateBuilder::Uninit(aIsFinal);
+}
+
+nsresult
+nsXULContentBuilder::BuildContentFromTemplate(nsIContent *aTemplateNode,
+ nsIContent *aResourceNode,
+ nsIContent *aRealNode,
+ bool aIsUnique,
+ bool aIsSelfReference,
+ nsIXULTemplateResult* aChild,
+ bool aNotify,
+ nsTemplateMatch* aMatch,
+ nsIContent** aContainer,
+ int32_t* aNewIndexInContainer)
+{
+ // This is the mother lode. Here is where we grovel through an
+ // element in the template, copying children from the template
+ // into the "real" content tree, performing substitution as we go
+ // by looking stuff up using the results.
+ //
+ // |aTemplateNode| is the element in the "template tree", whose
+ // children we will duplicate and move into the "real" content
+ // tree.
+ //
+ // |aResourceNode| is the element in the "real" content tree that
+ // has the "id" attribute set to an result's id. This is
+ // not directly used here, but rather passed down to the XUL
+ // sort service to perform container-level sort.
+ //
+ // |aRealNode| is the element in the "real" content tree to which
+ // the new elements will be copied.
+ //
+ // |aIsUnique| is set to "true" so long as content has been
+ // "unique" (or "above" the resource element) so far in the
+ // template.
+ //
+ // |aIsSelfReference| should be set to "true" for cases where
+ // the reference and member variables are the same, indicating
+ // that the generated node is the same as the reference point,
+ // so generation should not recurse, or else an infinite loop
+ // would occur.
+ //
+ // |aChild| is the result for which we are building content.
+ //
+ // |aNotify| is set to "true" if content should be constructed
+ // "noisily"; that is, whether the document observers should be
+ // notified when new content is added to the content model.
+ //
+ // |aContainer| is an out parameter that will be set to the first
+ // container element in the "real" content tree to which content
+ // was appended.
+ //
+ // |aNewIndexInContainer| is an out parameter that will be set to
+ // the index in aContainer at which new content is first
+ // constructed.
+ //
+ // If |aNotify| is "false", then |aContainer| and
+ // |aNewIndexInContainer| are used to determine where in the
+ // content model new content is constructed. This allows a single
+ // notification to be propagated to document observers.
+ //
+
+ nsresult rv;
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsXULContentBuilder::BuildContentFromTemplate (is unique: %d)",
+ aIsUnique));
+
+ nsAutoString id;
+ aChild->GetId(id);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("Tags: [Template: %s Resource: %s Real: %s] for id %s",
+ nsAtomCString(aTemplateNode->NodeInfo()->NameAtom()).get(),
+ nsAtomCString(aResourceNode->NodeInfo()->NameAtom()).get(),
+ nsAtomCString(aRealNode->NodeInfo()->NameAtom()).get(), NS_ConvertUTF16toUTF8(id).get()));
+ }
+
+ // Iterate through all of the template children, constructing
+ // "real" content model nodes for each "template" child.
+ for (nsIContent* tmplKid = aTemplateNode->GetFirstChild();
+ tmplKid;
+ tmplKid = tmplKid->GetNextSibling()) {
+
+ int32_t nameSpaceID = tmplKid->GetNameSpaceID();
+
+ // Check whether this element is the generation element. The generation
+ // element is the element that is cookie-cutter copied once for each
+ // different result specified by |aChild|.
+ //
+ // Nodes that appear -above- the generation element
+ // (that is, are ancestors of the generation element in the
+ // content model) are unique across all values of |aChild|,
+ // and are created only once.
+ //
+ // Nodes that appear -below- the generation element (that is,
+ // are descendants of the generation element in the content
+ // model), are cookie-cutter copied for each distinct value of
+ // |aChild|.
+ //
+ // For example, in a <tree> template:
+ //
+ // <tree>
+ // <template>
+ // <treechildren> [1]
+ // <treeitem uri="rdf:*"> [2]
+ // <treerow> [3]
+ // <treecell value="rdf:urn:foo" /> [4]
+ // <treecell value="rdf:urn:bar" /> [5]
+ // </treerow>
+ // </treeitem>
+ // </treechildren>
+ // </template>
+ // </tree>
+ //
+ // The <treeitem> element [2] is the generation element. This
+ // element, and all of its descendants ([3], [4], and [5])
+ // will be duplicated for each different |aChild|.
+ // It's ancestor <treechildren> [1] is unique, and
+ // will only be created -once-, no matter how many <treeitem>s
+ // are created below it.
+ //
+ // isUnique will be true for nodes above the generation element,
+ // isGenerationElement will be true for the generation element,
+ // and both will be false for descendants
+ bool isGenerationElement = false;
+ bool isUnique = aIsUnique;
+
+ // We identify the resource element by presence of a
+ // "uri='rdf:*'" attribute. (We also support the older
+ // "uri='...'" syntax.)
+ if (tmplKid->HasAttr(kNameSpaceID_None, nsGkAtoms::uri) && aMatch->IsActive()) {
+ isGenerationElement = true;
+ isUnique = false;
+ }
+
+ MOZ_ASSERT_IF(isGenerationElement, tmplKid->IsElement());
+
+ nsIAtom *tag = tmplKid->NodeInfo()->NameAtom();
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("xultemplate[%p] building %s %s %s",
+ this, nsAtomCString(tag).get(),
+ (isGenerationElement ? "[resource]" : ""),
+ (isUnique ? "[unique]" : "")));
+ }
+
+ // Set to true if the child we're trying to create now
+ // already existed in the content model.
+ bool realKidAlreadyExisted = false;
+
+ nsCOMPtr<nsIContent> realKid;
+ if (isUnique) {
+ // The content is "unique"; that is, we haven't descended
+ // far enough into the template to hit the generation
+ // element yet. |EnsureElementHasGenericChild()| will
+ // conditionally create the element iff it isn't there
+ // already.
+ rv = EnsureElementHasGenericChild(aRealNode, nameSpaceID, tag, aNotify, getter_AddRefs(realKid));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (rv == NS_ELEMENT_WAS_THERE) {
+ realKidAlreadyExisted = true;
+ }
+ else {
+ // Potentially remember the index of this element as the first
+ // element that we've generated. Note that we remember
+ // this -before- we recurse!
+ if (aContainer && !*aContainer) {
+ *aContainer = aRealNode;
+ NS_ADDREF(*aContainer);
+
+ uint32_t indx = aRealNode->GetChildCount();
+
+ // Since EnsureElementHasGenericChild() added us, make
+ // sure to subtract one for our real index.
+ *aNewIndexInContainer = indx - 1;
+ }
+ }
+
+ // Recurse until we get to the resource element. Since
+ // -we're- unique, assume that our child will be
+ // unique. The check for the "resource" element at the top
+ // of the function will trip this to |false| as soon as we
+ // encounter it.
+ rv = BuildContentFromTemplate(tmplKid, aResourceNode, realKid, true,
+ aIsSelfReference, aChild, aNotify, aMatch,
+ aContainer, aNewIndexInContainer);
+
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ else if (isGenerationElement) {
+ // It's the "resource" element. Create a new element using
+ // the namespace ID and tag from the template element.
+ nsCOMPtr<Element> element;
+ rv = CreateElement(nameSpaceID, tag, getter_AddRefs(element));
+ if (NS_FAILED(rv))
+ return rv;
+ realKid = element.forget();
+
+ // Add the resource element to the content support map so
+ // we can remove the match based on the content node later.
+ mContentSupportMap.Put(realKid, aMatch);
+
+ // Assign the element an 'id' attribute using result's id
+ nsAutoString id;
+ rv = aChild->GetId(id);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = realKid->SetAttr(kNameSpaceID_None, nsGkAtoms::id, id, false);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Set up the element's 'container' and 'empty' attributes.
+ SetContainerAttrs(realKid, aChild, true, false);
+ }
+ else if (tag == nsGkAtoms::textnode &&
+ nameSpaceID == kNameSpaceID_XUL) {
+ // <xul:text value="..."> is replaced by text of the
+ // actual value of the 'rdf:resource' attribute for the
+ // given node.
+ // SynchronizeUsingTemplate contains code used to update textnodes,
+ // so make sure to modify both when changing this
+ char16_t attrbuf[128];
+ nsFixedString attrValue(attrbuf, ArrayLength(attrbuf), 0);
+ tmplKid->GetAttr(kNameSpaceID_None, nsGkAtoms::value, attrValue);
+ if (!attrValue.IsEmpty()) {
+ nsAutoString value;
+ rv = SubstituteText(aChild, attrValue, value);
+ if (NS_FAILED(rv)) return rv;
+
+ RefPtr<nsTextNode> content =
+ new nsTextNode(mRoot->NodeInfo()->NodeInfoManager());
+
+ content->SetText(value, false);
+
+ rv = aRealNode->AppendChildTo(content, aNotify);
+ if (NS_FAILED(rv)) return rv;
+
+ // XXX Don't bother remembering text nodes as the
+ // first element we've generated?
+ }
+ }
+ else if (tmplKid->IsNodeOfType(nsINode::eTEXT)) {
+ nsCOMPtr<nsIDOMNode> tmplTextNode = do_QueryInterface(tmplKid);
+ if (!tmplTextNode) {
+ NS_ERROR("textnode not implementing nsIDOMNode??");
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIDOMNode> clonedNode;
+ tmplTextNode->CloneNode(false, 1, getter_AddRefs(clonedNode));
+ nsCOMPtr<nsIContent> clonedContent = do_QueryInterface(clonedNode);
+ if (!clonedContent) {
+ NS_ERROR("failed to clone textnode");
+ return NS_ERROR_FAILURE;
+ }
+ rv = aRealNode->AppendChildTo(clonedContent, aNotify);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else {
+ // It's just a generic element. Create it!
+ nsCOMPtr<Element> element;
+ rv = CreateElement(nameSpaceID, tag, getter_AddRefs(element));
+ if (NS_FAILED(rv)) return rv;
+ realKid = element.forget();
+ }
+
+ if (realKid && !realKidAlreadyExisted) {
+ // Potentially remember the index of this element as the
+ // first element that we've generated.
+ if (aContainer && !*aContainer) {
+ *aContainer = aRealNode;
+ NS_ADDREF(*aContainer);
+
+ uint32_t indx = aRealNode->GetChildCount();
+
+ // Since we haven't inserted any content yet, our new
+ // index in the container will be the current count of
+ // elements in the container.
+ *aNewIndexInContainer = indx;
+ }
+
+ // Remember the template kid from which we created the
+ // real kid. This allows us to sync back up with the
+ // template to incrementally build content.
+ mTemplateMap.Put(realKid, tmplKid);
+
+ rv = CopyAttributesToElement(tmplKid, realKid, aChild, false);
+ if (NS_FAILED(rv)) return rv;
+
+ // Add any persistent attributes
+ if (isGenerationElement) {
+ rv = AddPersistentAttributes(tmplKid->AsElement(), aChild,
+ realKid);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // the unique content recurses up above. Also, don't recurse if
+ // this is a self reference (a reference to the same resource)
+ // or we'll end up regenerating the same content.
+ if (!aIsSelfReference && !isUnique) {
+ // this call creates the content inside the generation node,
+ // for example the label below:
+ // <vbox uri="?">
+ // <label value="?title"/>
+ // </vbox>
+ rv = BuildContentFromTemplate(tmplKid, aResourceNode, realKid, false,
+ false, aChild, false, aMatch,
+ nullptr /* don't care */,
+ nullptr /* don't care */);
+ if (NS_FAILED(rv)) return rv;
+
+ if (isGenerationElement) {
+ // build the next level of children
+ rv = CreateContainerContents(realKid, aChild, false,
+ false, false);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+
+ // We'll _already_ have added the unique elements; but if
+ // it's -not- unique, then use the XUL sort service now to
+ // append the element to the content model.
+ if (! isUnique) {
+ rv = NS_ERROR_UNEXPECTED;
+
+ if (isGenerationElement)
+ rv = InsertSortedNode(aRealNode, realKid, aChild, aNotify);
+
+ if (NS_FAILED(rv)) {
+ rv = aRealNode->AppendChildTo(realKid, aNotify);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to insert element");
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::CopyAttributesToElement(nsIContent* aTemplateNode,
+ nsIContent* aRealNode,
+ nsIXULTemplateResult* aResult,
+ bool aNotify)
+{
+ nsresult rv;
+
+ // Copy all attributes from the template to the new element
+ uint32_t numAttribs = aTemplateNode->GetAttrCount();
+
+ for (uint32_t attr = 0; attr < numAttribs; attr++) {
+ const nsAttrName* name = aTemplateNode->GetAttrNameAt(attr);
+ int32_t attribNameSpaceID = name->NamespaceID();
+ // Hold a strong reference here so that the atom doesn't go away
+ // during UnsetAttr.
+ nsCOMPtr<nsIAtom> attribName = name->LocalName();
+
+ // XXXndeakin ignore namespaces until bug 321182 is fixed
+ if (attribName != nsGkAtoms::id && attribName != nsGkAtoms::uri) {
+ // Create a buffer here, because there's a chance that an
+ // attribute in the template is going to be an RDF URI, which is
+ // usually longish.
+ char16_t attrbuf[128];
+ nsFixedString attribValue(attrbuf, ArrayLength(attrbuf), 0);
+ aTemplateNode->GetAttr(attribNameSpaceID, attribName, attribValue);
+ if (!attribValue.IsEmpty()) {
+ nsAutoString value;
+ rv = SubstituteText(aResult, attribValue, value);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // if the string is empty after substitutions, remove the
+ // attribute
+ if (!value.IsEmpty()) {
+ rv = aRealNode->SetAttr(attribNameSpaceID,
+ attribName,
+ name->GetPrefix(),
+ value,
+ aNotify);
+ }
+ else {
+ rv = aRealNode->UnsetAttr(attribNameSpaceID,
+ attribName,
+ aNotify);
+ }
+
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::AddPersistentAttributes(Element* aTemplateNode,
+ nsIXULTemplateResult* aResult,
+ nsIContent* aRealNode)
+{
+ if (!mRoot)
+ return NS_OK;
+
+ nsCOMPtr<nsIRDFResource> resource;
+ nsresult rv = GetResultResource(aResult, getter_AddRefs(resource));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString attribute, persist;
+ aTemplateNode->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist);
+
+ while (!persist.IsEmpty()) {
+ attribute.Truncate();
+
+ int32_t offset = persist.FindCharInSet(" ,");
+ if (offset > 0) {
+ persist.Left(attribute, offset);
+ persist.Cut(0, offset + 1);
+ }
+ else {
+ attribute = persist;
+ persist.Truncate();
+ }
+
+ attribute.Trim(" ");
+
+ if (attribute.IsEmpty())
+ break;
+
+ nsCOMPtr<nsIAtom> tag;
+ int32_t nameSpaceID;
+
+ RefPtr<mozilla::dom::NodeInfo> ni =
+ aTemplateNode->GetExistingAttrNameFromQName(attribute);
+ if (ni) {
+ tag = ni->NameAtom();
+ nameSpaceID = ni->NamespaceID();
+ }
+ else {
+ tag = NS_Atomize(attribute);
+ NS_ENSURE_TRUE(tag, NS_ERROR_OUT_OF_MEMORY);
+
+ nameSpaceID = kNameSpaceID_None;
+ }
+
+ nsCOMPtr<nsIRDFResource> property;
+ rv = nsXULContentUtils::GetResource(nameSpaceID, tag, getter_AddRefs(property));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRDFNode> target;
+ rv = mDB->GetTarget(resource, property, true, getter_AddRefs(target));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (! target)
+ continue;
+
+ nsCOMPtr<nsIRDFLiteral> value = do_QueryInterface(target);
+ NS_ASSERTION(value != nullptr, "unable to stomach that sort of node");
+ if (! value)
+ continue;
+
+ const char16_t* valueStr;
+ rv = value->GetValueConst(&valueStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aRealNode->SetAttr(nameSpaceID, tag, nsDependentString(valueStr),
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::SynchronizeUsingTemplate(nsIContent* aTemplateNode,
+ nsIContent* aRealElement,
+ nsIXULTemplateResult* aResult)
+{
+ // check all attributes on the template node; if they reference a resource,
+ // update the equivalent attribute on the content node
+ nsresult rv;
+ rv = CopyAttributesToElement(aTemplateNode, aRealElement, aResult, true);
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t count = aTemplateNode->GetChildCount();
+
+ for (uint32_t loop = 0; loop < count; ++loop) {
+ nsIContent *tmplKid = aTemplateNode->GetChildAt(loop);
+
+ if (! tmplKid)
+ break;
+
+ nsIContent *realKid = aRealElement->GetChildAt(loop);
+ if (! realKid)
+ break;
+
+ // check for text nodes and update them accordingly.
+ // This code is similar to that in BuildContentFromTemplate
+ if (tmplKid->NodeInfo()->Equals(nsGkAtoms::textnode,
+ kNameSpaceID_XUL)) {
+ char16_t attrbuf[128];
+ nsFixedString attrValue(attrbuf, ArrayLength(attrbuf), 0);
+ tmplKid->GetAttr(kNameSpaceID_None, nsGkAtoms::value, attrValue);
+ if (!attrValue.IsEmpty()) {
+ nsAutoString value;
+ rv = SubstituteText(aResult, attrValue, value);
+ if (NS_FAILED(rv)) return rv;
+ realKid->SetText(value, true);
+ }
+ }
+
+ rv = SynchronizeUsingTemplate(tmplKid, realKid, aResult);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::RemoveMember(nsIContent* aContent)
+{
+ nsCOMPtr<nsIContent> parent = aContent->GetParent();
+ if (parent) {
+ int32_t pos = parent->IndexOf(aContent);
+
+ NS_ASSERTION(pos >= 0, "parent doesn't think this child has an index");
+ if (pos < 0) return NS_OK;
+
+ // Note: RemoveChildAt sets |child|'s document to null so that
+ // it'll get knocked out of the XUL doc's resource-to-element
+ // map.
+ parent->RemoveChildAt(pos, true);
+ }
+
+ // Remove from the content support map.
+ mContentSupportMap.Remove(aContent);
+
+ // Remove from the template map
+ mTemplateMap.Remove(aContent);
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::CreateTemplateAndContainerContents(nsIContent* aElement,
+ bool aForceCreation)
+{
+ // Generate both 1) the template content for the current element,
+ // and 2) recursive subcontent (if the current element refers to a
+ // container result).
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Info,
+ ("nsXULContentBuilder::CreateTemplateAndContainerContents start - flags: %d",
+ mFlags));
+
+ if (! mQueryProcessor)
+ return NS_OK;
+
+ // for the root element, get the ref attribute and generate content
+ if (aElement == mRoot) {
+ if (! mRootResult) {
+ nsAutoString ref;
+ mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::ref, ref);
+
+ if (! ref.IsEmpty()) {
+ nsresult rv = mQueryProcessor->TranslateRef(mDataSource, ref,
+ getter_AddRefs(mRootResult));
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ }
+
+ if (mRootResult) {
+ CreateContainerContents(aElement, mRootResult, aForceCreation,
+ false, true);
+ }
+ }
+ else if (!(mFlags & eDontRecurse)) {
+ // The content map will contain the generation elements (the ones that
+ // are given ids) and only those elements, so get the reference point
+ // from the corresponding match.
+ nsTemplateMatch *match = nullptr;
+ if (mContentSupportMap.Get(aElement, &match))
+ CreateContainerContents(aElement, match->mResult, aForceCreation,
+ false, true);
+ }
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Info,
+ ("nsXULContentBuilder::CreateTemplateAndContainerContents end"));
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::CreateContainerContents(nsIContent* aElement,
+ nsIXULTemplateResult* aResult,
+ bool aForceCreation,
+ bool aNotify,
+ bool aNotifyAtEnd)
+{
+ if (!aForceCreation && !IsOpen(aElement))
+ return NS_OK;
+
+ // don't generate children if recursion or child processing isn't allowed
+ if (aResult != mRootResult) {
+ if (mFlags & eDontRecurse)
+ return NS_OK;
+
+ bool mayProcessChildren;
+ nsresult rv = aResult->GetMayProcessChildren(&mayProcessChildren);
+ if (NS_FAILED(rv) || !mayProcessChildren)
+ return rv;
+ }
+
+ nsCOMPtr<nsIRDFResource> refResource;
+ GetResultResource(aResult, getter_AddRefs(refResource));
+ if (! refResource)
+ return NS_ERROR_FAILURE;
+
+ // Avoid re-entrant builds for the same resource.
+ if (IsActivated(refResource))
+ return NS_OK;
+
+ ActivationEntry entry(refResource, &mTop);
+
+ // Compile the rules now, if they haven't been already.
+ if (! mQueriesCompiled) {
+ nsresult rv = CompileQueries();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ if (mQuerySets.Length() == 0)
+ return NS_OK;
+
+ // See if the element's templates contents have been generated:
+ // this prevents a re-entrant call from triggering another
+ // generation.
+ nsXULElement *xulcontent = nsXULElement::FromContent(aElement);
+ if (xulcontent) {
+ if (xulcontent->GetTemplateGenerated())
+ return NS_OK;
+
+ // Now mark the element's contents as being generated so that
+ // any re-entrant calls don't trigger an infinite recursion.
+ xulcontent->SetTemplateGenerated();
+ }
+
+ int32_t newIndexInContainer = -1;
+ nsIContent* container = nullptr;
+
+ int32_t querySetCount = mQuerySets.Length();
+
+ for (int32_t r = 0; r < querySetCount; r++) {
+ nsTemplateQuerySet* queryset = mQuerySets[r];
+
+ nsIAtom* tag = queryset->GetTag();
+ if (tag && tag != aElement->NodeInfo()->NameAtom())
+ continue;
+
+ CreateContainerContentsForQuerySet(aElement, aResult, aNotify, queryset,
+ &container, &newIndexInContainer);
+ }
+
+ if (aNotifyAtEnd && container) {
+ MOZ_AUTO_DOC_UPDATE(container->GetUncomposedDoc(), UPDATE_CONTENT_MODEL,
+ true);
+ nsNodeUtils::ContentAppended(container,
+ container->GetChildAt(newIndexInContainer),
+ newIndexInContainer);
+ }
+
+ NS_IF_RELEASE(container);
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::CreateContainerContentsForQuerySet(nsIContent* aElement,
+ nsIXULTemplateResult* aResult,
+ bool aNotify,
+ nsTemplateQuerySet* aQuerySet,
+ nsIContent** aContainer,
+ int32_t* aNewIndexInContainer)
+{
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ nsAutoString id;
+ aResult->GetId(id);
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsXULContentBuilder::CreateContainerContentsForQuerySet start for ref %s\n",
+ NS_ConvertUTF16toUTF8(id).get()));
+ }
+
+ if (! mQueryProcessor)
+ return NS_OK;
+
+ nsCOMPtr<nsISimpleEnumerator> results;
+ nsresult rv = mQueryProcessor->GenerateResults(mDataSource, aResult,
+ aQuerySet->mCompiledQuery,
+ getter_AddRefs(results));
+ if (NS_FAILED(rv) || !results)
+ return rv;
+
+ bool hasMoreResults;
+ rv = results->HasMoreElements(&hasMoreResults);
+
+ for (; NS_SUCCEEDED(rv) && hasMoreResults;
+ rv = results->HasMoreElements(&hasMoreResults)) {
+ nsCOMPtr<nsISupports> nr;
+ rv = results->GetNext(getter_AddRefs(nr));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIXULTemplateResult> nextresult = do_QueryInterface(nr);
+ if (!nextresult)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIRDFResource> resultid;
+ rv = GetResultResource(nextresult, getter_AddRefs(resultid));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!resultid)
+ continue;
+
+ nsTemplateMatch *newmatch =
+ nsTemplateMatch::Create(aQuerySet->Priority(),
+ nextresult, aElement);
+ if (!newmatch)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // check if there is already an existing match. If so, a previous
+ // query already generated content so the match is just added to the
+ // end of the set of matches.
+
+ bool generateContent = true;
+
+ nsTemplateMatch* prevmatch = nullptr;
+ nsTemplateMatch* existingmatch = nullptr;
+ nsTemplateMatch* removematch = nullptr;
+ if (mMatchMap.Get(resultid, &existingmatch)){
+ // check if there is an existing match that matched a rule
+ while (existingmatch) {
+ // break out once we've reached a query in the list with a
+ // higher priority, as the new match list is sorted by
+ // priority, and the new match should be inserted here
+ int32_t priority = existingmatch->QuerySetPriority();
+ if (priority > aQuerySet->Priority())
+ break;
+
+ // skip over non-matching containers
+ if (existingmatch->GetContainer() == aElement) {
+ // if the same priority is already found, replace it. This can happen
+ // when a container is removed and readded
+ if (priority == aQuerySet->Priority()) {
+ removematch = existingmatch;
+ break;
+ }
+
+ if (existingmatch->IsActive())
+ generateContent = false;
+ }
+
+ prevmatch = existingmatch;
+ existingmatch = existingmatch->mNext;
+ }
+ }
+
+ if (removematch) {
+ // remove the generated content for the existing match
+ rv = ReplaceMatch(removematch->mResult, nullptr, nullptr, aElement);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mFlags & eLoggingEnabled)
+ OutputMatchToLog(resultid, removematch, false);
+ }
+
+ if (generateContent) {
+ // find the rule that matches. If none match, the content does not
+ // need to be generated
+
+ int16_t ruleindex;
+ nsTemplateRule* matchedrule = nullptr;
+ rv = DetermineMatchedRule(aElement, nextresult, aQuerySet,
+ &matchedrule, &ruleindex);
+ if (NS_FAILED(rv)) {
+ nsTemplateMatch::Destroy(newmatch, false);
+ return rv;
+ }
+
+ if (matchedrule) {
+ rv = newmatch->RuleMatched(aQuerySet, matchedrule,
+ ruleindex, nextresult);
+ if (NS_FAILED(rv)) {
+ nsTemplateMatch::Destroy(newmatch, false);
+ return rv;
+ }
+
+ // Grab the template node
+ nsCOMPtr<nsIContent> action = matchedrule->GetAction();
+ BuildContentFromTemplate(action, aElement, aElement, true,
+ mRefVariable == matchedrule->GetMemberVariable(),
+ nextresult, aNotify, newmatch,
+ aContainer, aNewIndexInContainer);
+ }
+ }
+
+ if (mFlags & eLoggingEnabled)
+ OutputMatchToLog(resultid, newmatch, true);
+
+ if (prevmatch) {
+ prevmatch->mNext = newmatch;
+ }
+ else {
+ mMatchMap.Put(resultid, newmatch);
+ }
+
+ if (removematch) {
+ newmatch->mNext = removematch->mNext;
+ nsTemplateMatch::Destroy(removematch, true);
+ }
+ else {
+ newmatch->mNext = existingmatch;
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+nsXULContentBuilder::EnsureElementHasGenericChild(nsIContent* parent,
+ int32_t nameSpaceID,
+ nsIAtom* tag,
+ bool aNotify,
+ nsIContent** result)
+{
+ nsresult rv;
+
+ rv = nsXULContentUtils::FindChildByTag(parent, nameSpaceID, tag, result);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (rv == NS_RDF_NO_VALUE) {
+ // we need to construct a new child element.
+ nsCOMPtr<Element> element;
+
+ rv = CreateElement(nameSpaceID, tag, getter_AddRefs(element));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // XXX Note that the notification ensures we won't batch insertions! This could be bad! - Dave
+ rv = parent->AppendChildTo(element, aNotify);
+ if (NS_FAILED(rv))
+ return rv;
+
+ element.forget(result);
+ return NS_ELEMENT_GOT_CREATED;
+ }
+ else {
+ return NS_ELEMENT_WAS_THERE;
+ }
+}
+
+bool
+nsXULContentBuilder::IsOpen(nsIContent* aElement)
+{
+ // Determine if this is a <treeitem> or <menu> element
+
+ // XXXhyatt Use the XBL service to obtain a base tag.
+ if (aElement->IsAnyOfXULElements(nsGkAtoms::menu,
+ nsGkAtoms::menubutton,
+ nsGkAtoms::toolbarbutton,
+ nsGkAtoms::button,
+ nsGkAtoms::treeitem))
+ return aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open,
+ nsGkAtoms::_true, eCaseMatters);
+ return true;
+}
+
+nsresult
+nsXULContentBuilder::RemoveGeneratedContent(nsIContent* aElement)
+{
+ // Keep a queue of "ungenerated" elements that we have to probe
+ // for generated content.
+ AutoTArray<nsIContent*, 8> ungenerated;
+ if (ungenerated.AppendElement(aElement) == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ uint32_t count;
+ while (0 != (count = ungenerated.Length())) {
+ // Pull the next "ungenerated" element off the queue.
+ uint32_t last = count - 1;
+ nsCOMPtr<nsIContent> element = ungenerated[last];
+ ungenerated.RemoveElementAt(last);
+
+ uint32_t i = element->GetChildCount();
+
+ while (i-- > 0) {
+ nsCOMPtr<nsIContent> child = element->GetChildAt(i);
+
+ // Optimize for the <template> element, because we *know*
+ // it won't have any generated content: there's no reason
+ // to even check this subtree.
+ // XXX should this check |child| rather than |element|? Otherwise
+ // it should be moved outside the inner loop. Bug 297290.
+ if (element->NodeInfo()->Equals(nsGkAtoms::_template,
+ kNameSpaceID_XUL) ||
+ !element->IsElement())
+ continue;
+
+ // If the element is in the template map, then we
+ // assume it's been generated and nuke it.
+ nsCOMPtr<nsIContent> tmpl;
+ mTemplateMap.GetTemplateFor(child, getter_AddRefs(tmpl));
+
+ if (! tmpl) {
+ // No 'template' attribute, so this must not have been
+ // generated. We'll need to examine its kids.
+ if (ungenerated.AppendElement(child) == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ continue;
+ }
+
+ // If we get here, it's "generated". Bye bye!
+ element->RemoveChildAt(i, true);
+
+ // Remove this and any children from the content support map.
+ mContentSupportMap.Remove(child);
+
+ // Remove from the template map
+ mTemplateMap.Remove(child);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::GetElementsForResult(nsIXULTemplateResult* aResult,
+ nsCOMArray<nsIContent>& aElements)
+{
+ // if the root has been removed from the document, just return
+ // since there won't be any generated content any more
+ nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(mRoot->GetComposedDoc());
+ if (! xuldoc)
+ return NS_OK;
+
+ nsAutoString id;
+ aResult->GetId(id);
+
+ xuldoc->GetElementsForID(id, aElements);
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::CreateElement(int32_t aNameSpaceID,
+ nsIAtom* aTag,
+ Element** aResult)
+{
+ nsCOMPtr<nsIDocument> doc = mRoot->GetComposedDoc();
+ NS_ASSERTION(doc != nullptr, "not initialized");
+ if (! doc)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo =
+ doc->NodeInfoManager()->GetNodeInfo(aTag, nullptr, aNameSpaceID,
+ nsIDOMNode::ELEMENT_NODE);
+
+ return NS_NewElement(aResult, nodeInfo.forget(), NOT_FROM_PARSER);
+}
+
+nsresult
+nsXULContentBuilder::SetContainerAttrs(nsIContent *aElement,
+ nsIXULTemplateResult* aResult,
+ bool aIgnoreNonContainers,
+ bool aNotify)
+{
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ bool iscontainer;
+ aResult->GetIsContainer(&iscontainer);
+
+ if (aIgnoreNonContainers && !iscontainer)
+ return NS_OK;
+
+ NS_NAMED_LITERAL_STRING(true_, "true");
+ NS_NAMED_LITERAL_STRING(false_, "false");
+
+ const nsAString& newcontainer =
+ iscontainer ? true_ : false_;
+
+ aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::container,
+ newcontainer, aNotify);
+
+ if (iscontainer && !(mFlags & eDontTestEmpty)) {
+ bool isempty;
+ aResult->GetIsEmpty(&isempty);
+
+ const nsAString& newempty =
+ (iscontainer && isempty) ? true_ : false_;
+
+ aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::empty,
+ newempty, aNotify);
+ }
+
+ return NS_OK;
+}
+
+
+//----------------------------------------------------------------------
+//
+// nsIXULTemplateBuilder methods
+//
+
+NS_IMETHODIMP
+nsXULContentBuilder::CreateContents(nsIContent* aElement, bool aForceCreation)
+{
+ NS_PRECONDITION(aElement != nullptr, "null ptr");
+ if (! aElement)
+ return NS_ERROR_NULL_POINTER;
+
+ // don't build contents for closed elements. aForceCreation will be true
+ // when a menu is about to be opened, so the content should be built anyway.
+ if (!aForceCreation && !IsOpen(aElement))
+ return NS_OK;
+
+ return CreateTemplateAndContainerContents(aElement, aForceCreation);
+}
+
+NS_IMETHODIMP
+nsXULContentBuilder::HasGeneratedContent(nsIRDFResource* aResource,
+ nsIAtom* aTag,
+ bool* aGenerated)
+{
+ *aGenerated = false;
+ NS_ENSURE_TRUE(mRoot, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_STATE(mRootResult);
+
+ nsCOMPtr<nsIRDFResource> rootresource;
+ nsresult rv = mRootResult->GetResource(getter_AddRefs(rootresource));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // the root resource is always acceptable
+ if (aResource == rootresource) {
+ if (!aTag || mRoot->NodeInfo()->NameAtom() == aTag)
+ *aGenerated = true;
+ }
+ else {
+ const char* uri;
+ aResource->GetValueConst(&uri);
+
+ NS_ConvertUTF8toUTF16 refID(uri);
+
+ // just return if the node is no longer in a document
+ nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(mRoot->GetComposedDoc());
+ if (! xuldoc)
+ return NS_OK;
+
+ nsCOMArray<nsIContent> elements;
+ xuldoc->GetElementsForID(refID, elements);
+
+ uint32_t cnt = elements.Count();
+
+ for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) {
+ nsCOMPtr<nsIContent> content = elements.SafeObjectAt(i);
+
+ do {
+ nsTemplateMatch* match;
+ if (content == mRoot || mContentSupportMap.Get(content, &match)) {
+ // If we've got a tag, check it to ensure we're consistent.
+ if (!aTag || content->NodeInfo()->NameAtom() == aTag) {
+ *aGenerated = true;
+ return NS_OK;
+ }
+ }
+
+ content = content->GetParent();
+ } while (content);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULContentBuilder::GetResultForContent(nsIDOMElement* aElement,
+ nsIXULTemplateResult** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aElement);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
+ if (content == mRoot) {
+ *aResult = mRootResult;
+ }
+ else {
+ nsTemplateMatch *match = nullptr;
+ if (mContentSupportMap.Get(content, &match))
+ *aResult = match->mResult;
+ else
+ *aResult = nullptr;
+ }
+
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+//
+// nsIDocumentObserver methods
+//
+
+void
+nsXULContentBuilder::AttributeChanged(nsIDocument* aDocument,
+ Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+
+ // Handle "open" and "close" cases. We do this handling before
+ // we've notified the observer, so that content is already created
+ // for the frame system to walk.
+ if (aElement->GetNameSpaceID() == kNameSpaceID_XUL &&
+ aAttribute == nsGkAtoms::open) {
+ // We're on a XUL tag, and an ``open'' attribute changed.
+ if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open,
+ nsGkAtoms::_true, eCaseMatters))
+ OpenContainer(aElement);
+ else
+ CloseContainer(aElement);
+ }
+
+ if ((aNameSpaceID == kNameSpaceID_XUL) &&
+ ((aAttribute == nsGkAtoms::sort) ||
+ (aAttribute == nsGkAtoms::sortDirection) ||
+ (aAttribute == nsGkAtoms::sortResource) ||
+ (aAttribute == nsGkAtoms::sortResource2)))
+ mSortState.initialized = false;
+
+ // Pass along to the generic template builder.
+ nsXULTemplateBuilder::AttributeChanged(aDocument, aElement, aNameSpaceID,
+ aAttribute, aModType, aOldValue);
+}
+
+void
+nsXULContentBuilder::NodeWillBeDestroyed(const nsINode* aNode)
+{
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ // Break circular references
+ mContentSupportMap.Clear();
+
+ nsXULTemplateBuilder::NodeWillBeDestroyed(aNode);
+}
+
+
+//----------------------------------------------------------------------
+//
+// nsXULTemplateBuilder methods
+//
+
+bool
+nsXULContentBuilder::GetInsertionLocations(nsIXULTemplateResult* aResult,
+ nsCOMArray<nsIContent>** aLocations)
+{
+ *aLocations = nullptr;
+
+ nsAutoString ref;
+ nsresult rv = aResult->GetBindingFor(mRefVariable, ref);
+ if (NS_FAILED(rv))
+ return false;
+
+ nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(mRoot->GetComposedDoc());
+ if (! xuldoc)
+ return false;
+
+ *aLocations = new nsCOMArray<nsIContent>;
+ NS_ENSURE_TRUE(*aLocations, false);
+
+ xuldoc->GetElementsForID(ref, **aLocations);
+ uint32_t count = (*aLocations)->Count();
+
+ bool found = false;
+
+ for (uint32_t t = 0; t < count; t++) {
+ nsCOMPtr<nsIContent> content = (*aLocations)->SafeObjectAt(t);
+
+ nsTemplateMatch* refmatch;
+ if (content == mRoot || mContentSupportMap.Get(content, &refmatch)) {
+ // See if we've built the container contents for "content"
+ // yet. If not, we don't need to build any content. This
+ // happens, for example, if we receive an assertion on a
+ // closed folder in a tree widget or on a menu that hasn't
+ // yet been opened.
+ nsXULElement *xulcontent = nsXULElement::FromContent(content);
+ if (!xulcontent || xulcontent->GetTemplateGenerated()) {
+ found = true;
+ continue;
+ }
+ }
+
+ // clear the item in the list since we don't want to insert there
+ (*aLocations)->ReplaceObjectAt(nullptr, t);
+ }
+
+ return found;
+}
+
+nsresult
+nsXULContentBuilder::ReplaceMatch(nsIXULTemplateResult* aOldResult,
+ nsTemplateMatch* aNewMatch,
+ nsTemplateRule* aNewMatchRule,
+ void *aContext)
+
+{
+ nsresult rv;
+ nsIContent* content = static_cast<nsIContent*>(aContext);
+
+ // update the container attributes for the match
+ if (content) {
+ nsAutoString ref;
+ if (aNewMatch)
+ rv = aNewMatch->mResult->GetBindingFor(mRefVariable, ref);
+ else
+ rv = aOldResult->GetBindingFor(mRefVariable, ref);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!ref.IsEmpty()) {
+ nsCOMPtr<nsIXULTemplateResult> refResult;
+ rv = GetResultForId(ref, getter_AddRefs(refResult));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (refResult)
+ SetContainerAttrs(content, refResult, false, true);
+ }
+ }
+
+ if (aOldResult) {
+ nsCOMArray<nsIContent> elements;
+ rv = GetElementsForResult(aOldResult, elements);
+ if (NS_FAILED(rv))
+ return rv;
+
+ uint32_t count = elements.Count();
+
+ for (int32_t e = int32_t(count) - 1; e >= 0; --e) {
+ nsCOMPtr<nsIContent> child = elements.SafeObjectAt(e);
+
+ nsTemplateMatch* match;
+ if (mContentSupportMap.Get(child, &match)) {
+ if (content == match->GetContainer())
+ RemoveMember(child);
+ }
+ }
+ }
+
+ if (aNewMatch) {
+ nsCOMPtr<nsIContent> action = aNewMatchRule->GetAction();
+ return BuildContentFromTemplate(action, content, content, true,
+ mRefVariable == aNewMatchRule->GetMemberVariable(),
+ aNewMatch->mResult, true, aNewMatch,
+ nullptr, nullptr);
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+nsXULContentBuilder::SynchronizeResult(nsIXULTemplateResult* aResult)
+{
+ nsCOMArray<nsIContent> elements;
+ GetElementsForResult(aResult, elements);
+
+ uint32_t cnt = elements.Count();
+
+ for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) {
+ nsCOMPtr<nsIContent> element = elements.SafeObjectAt(i);
+
+ nsTemplateMatch* match;
+ if (! mContentSupportMap.Get(element, &match))
+ continue;
+
+ nsCOMPtr<nsIContent> templateNode;
+ mTemplateMap.GetTemplateFor(element, getter_AddRefs(templateNode));
+
+ NS_ASSERTION(templateNode, "couldn't find template node for element");
+ if (! templateNode)
+ continue;
+
+ // this node was created by a XUL template, so update it accordingly
+ SynchronizeUsingTemplate(templateNode, element, aResult);
+ }
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+//
+// Implementation methods
+//
+
+nsresult
+nsXULContentBuilder::OpenContainer(nsIContent* aElement)
+{
+ if (aElement != mRoot) {
+ if (mFlags & eDontRecurse)
+ return NS_OK;
+
+ bool rightBuilder = false;
+
+ nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(aElement->GetComposedDoc());
+ if (! xuldoc)
+ return NS_OK;
+
+ // See if we're responsible for this element
+ nsIContent* content = aElement;
+ do {
+ nsCOMPtr<nsIXULTemplateBuilder> builder;
+ xuldoc->GetTemplateBuilderFor(content, getter_AddRefs(builder));
+ if (builder) {
+ if (builder == this)
+ rightBuilder = true;
+ break;
+ }
+
+ content = content->GetParent();
+ } while (content);
+
+ if (! rightBuilder)
+ return NS_OK;
+ }
+
+ CreateTemplateAndContainerContents(aElement, false);
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::CloseContainer(nsIContent* aElement)
+{
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::RebuildAll()
+{
+ NS_ENSURE_TRUE(mRoot, NS_ERROR_NOT_INITIALIZED);
+
+ // Bail out early if we are being torn down.
+ nsCOMPtr<nsIDocument> doc = mRoot->GetComposedDoc();
+ if (!doc)
+ return NS_OK;
+
+ if (mQueriesCompiled)
+ Uninit(false);
+
+ nsresult rv = CompileQueries();
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mQuerySets.Length() == 0)
+ return NS_OK;
+
+ nsXULElement *xulcontent = nsXULElement::FromContent(mRoot);
+ if (xulcontent)
+ xulcontent->ClearTemplateGenerated();
+
+ // Now, regenerate both the template- and container-generated
+ // contents for the current element...
+ CreateTemplateAndContainerContents(mRoot, false);
+
+ return NS_OK;
+}
+
+/**** Sorting Methods ****/
+
+nsresult
+nsXULContentBuilder::CompareResultToNode(nsIXULTemplateResult* aResult,
+ nsIContent* aContent,
+ int32_t* aSortOrder)
+{
+ NS_ASSERTION(aSortOrder, "CompareResultToNode: null out param aSortOrder");
+
+ *aSortOrder = 0;
+
+ nsTemplateMatch *match = nullptr;
+ if (!mContentSupportMap.Get(aContent, &match)) {
+ *aSortOrder = mSortState.sortStaticsLast ? -1 : 1;
+ return NS_OK;
+ }
+
+ if (!mQueryProcessor)
+ return NS_OK;
+
+ if (mSortState.direction == nsSortState_natural) {
+ // sort in natural order
+ nsresult rv = mQueryProcessor->CompareResults(aResult, match->mResult,
+ nullptr, mSortState.sortHints,
+ aSortOrder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ // iterate over each sort key and compare. If the nodes are equal,
+ // continue to compare using the next sort key. If not equal, stop.
+ int32_t length = mSortState.sortKeys.Count();
+ for (int32_t t = 0; t < length; t++) {
+ nsresult rv = mQueryProcessor->CompareResults(aResult, match->mResult,
+ mSortState.sortKeys[t],
+ mSortState.sortHints, aSortOrder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*aSortOrder)
+ break;
+ }
+ }
+
+ // flip the sort order if performing a descending sorting
+ if (mSortState.direction == nsSortState_descending)
+ *aSortOrder = -*aSortOrder;
+
+ return NS_OK;
+}
+
+nsresult
+nsXULContentBuilder::InsertSortedNode(nsIContent* aContainer,
+ nsIContent* aNode,
+ nsIXULTemplateResult* aResult,
+ bool aNotify)
+{
+ nsresult rv;
+
+ if (!mSortState.initialized) {
+ nsAutoString sort, sortDirection, sortHints;
+ mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort);
+ mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, sortDirection);
+ mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::sorthints, sortHints);
+ sortDirection += ' ';
+ sortDirection += sortHints;
+ rv = XULSortServiceImpl::InitializeSortState(mRoot, aContainer,
+ sort, sortDirection, &mSortState);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // when doing a natural sort, items will typically be sorted according to
+ // the order they appear in the datasource. For RDF, cache whether the
+ // reference parent is an RDF Seq. That way, the items can be sorted in the
+ // order they are in the Seq.
+ mSortState.isContainerRDFSeq = false;
+ if (mSortState.direction == nsSortState_natural) {
+ nsCOMPtr<nsISupports> ref;
+ nsresult rv = aResult->GetBindingObjectFor(mRefVariable, getter_AddRefs(ref));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRDFResource> container = do_QueryInterface(ref);
+
+ if (container) {
+ rv = gRDFContainerUtils->IsSeq(mDB, container, &mSortState.isContainerRDFSeq);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ bool childAdded = false;
+ uint32_t numChildren = aContainer->GetChildCount();
+
+ if (mSortState.direction != nsSortState_natural ||
+ (mSortState.direction == nsSortState_natural && mSortState.isContainerRDFSeq))
+ {
+ // because numChildren gets modified
+ int32_t realNumChildren = numChildren;
+ nsIContent *child = nullptr;
+
+ // rjc says: determine where static XUL ends and generated XUL/RDF begins
+ int32_t staticCount = 0;
+
+ nsAutoString staticValue;
+ aContainer->GetAttr(kNameSpaceID_None, nsGkAtoms::staticHint, staticValue);
+ if (!staticValue.IsEmpty())
+ {
+ // found "static" XUL element count hint
+ nsresult strErr = NS_OK;
+ staticCount = staticValue.ToInteger(&strErr);
+ if (NS_FAILED(strErr))
+ staticCount = 0;
+ } else {
+ // compute the "static" XUL element count
+ for (nsIContent* child = aContainer->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+
+ if (nsContentUtils::HasNonEmptyAttr(child, kNameSpaceID_None,
+ nsGkAtoms::_template))
+ break;
+ else
+ ++staticCount;
+ }
+
+ if (mSortState.sortStaticsLast) {
+ // indicate that static XUL comes after RDF-generated content by
+ // making negative
+ staticCount = -staticCount;
+ }
+
+ // save the "static" XUL element count hint
+ nsAutoString valueStr;
+ valueStr.AppendInt(staticCount);
+ aContainer->SetAttr(kNameSpaceID_None, nsGkAtoms::staticHint, valueStr, false);
+ }
+
+ if (staticCount <= 0) {
+ numChildren += staticCount;
+ staticCount = 0;
+ } else if (staticCount > (int32_t)numChildren) {
+ staticCount = numChildren;
+ numChildren -= staticCount;
+ }
+
+ // figure out where to insert the node when a sort order is being imposed
+ if (numChildren > 0) {
+ nsIContent *temp;
+ int32_t direction;
+
+ // rjc says: The following is an implementation of a fairly optimal
+ // binary search insertion sort... with interpolation at either end-point.
+
+ if (mSortState.lastWasFirst) {
+ child = aContainer->GetChildAt(staticCount);
+ temp = child;
+ rv = CompareResultToNode(aResult, temp, &direction);
+ if (direction < 0) {
+ aContainer->InsertChildAt(aNode, staticCount, aNotify);
+ childAdded = true;
+ } else
+ mSortState.lastWasFirst = false;
+ } else if (mSortState.lastWasLast) {
+ child = aContainer->GetChildAt(realNumChildren - 1);
+ temp = child;
+ rv = CompareResultToNode(aResult, temp, &direction);
+ if (direction > 0) {
+ aContainer->InsertChildAt(aNode, realNumChildren, aNotify);
+ childAdded = true;
+ } else
+ mSortState.lastWasLast = false;
+ }
+
+ int32_t left = staticCount + 1, right = realNumChildren, x;
+ while (!childAdded && right >= left) {
+ x = (left + right) / 2;
+ child = aContainer->GetChildAt(x - 1);
+ temp = child;
+
+ rv = CompareResultToNode(aResult, temp, &direction);
+ if ((x == left && direction < 0) ||
+ (x == right && direction >= 0) ||
+ left == right)
+ {
+ int32_t thePos = (direction > 0 ? x : x - 1);
+ aContainer->InsertChildAt(aNode, thePos, aNotify);
+ childAdded = true;
+
+ mSortState.lastWasFirst = (thePos == staticCount);
+ mSortState.lastWasLast = (thePos >= realNumChildren);
+
+ break;
+ }
+ if (direction < 0)
+ right = x - 1;
+ else
+ left = x + 1;
+ }
+ }
+ }
+
+ // if the child hasn't been inserted yet, just add it at the end. Note
+ // that an append isn't done as there may be static content afterwards.
+ if (!childAdded)
+ aContainer->InsertChildAt(aNode, numChildren, aNotify);
+
+ return NS_OK;
+}
diff --git a/dom/xul/templates/nsXULContentUtils.cpp b/dom/xul/templates/nsXULContentUtils.cpp
new file mode 100644
index 000000000..5e128b42e
--- /dev/null
+++ b/dom/xul/templates/nsXULContentUtils.cpp
@@ -0,0 +1,366 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ *
+ * This Original Code has been modified by IBM Corporation.
+ * Modifications made by IBM described herein are
+ * Copyright (c) International Business Machines
+ * Corporation, 2000
+ *
+ * Modifications to Mozilla code or documentation
+ * identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink
+ * use in OS2
+ */
+
+
+/*
+
+ A package of routines shared by the XUL content code.
+
+ */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMXULCommandDispatcher.h"
+#include "nsIDOMXULDocument.h"
+#include "nsIRDFNode.h"
+#include "nsIRDFService.h"
+#include "nsIServiceManager.h"
+#include "nsIURL.h"
+#include "nsXULContentUtils.h"
+#include "nsLayoutCID.h"
+#include "nsNameSpaceManager.h"
+#include "nsRDFCID.h"
+#include "nsString.h"
+#include "nsXPIDLString.h"
+#include "nsGkAtoms.h"
+#include "mozilla/Logging.h"
+#include "prtime.h"
+#include "rdf.h"
+#include "nsContentUtils.h"
+#include "nsIDateTimeFormat.h"
+#include "nsIScriptableDateFormat.h"
+#include "nsICollation.h"
+#include "nsCollationCID.h"
+#include "nsILocale.h"
+#include "nsILocaleService.h"
+#include "nsIConsoleService.h"
+#include "nsEscape.h"
+
+using namespace mozilla;
+
+//------------------------------------------------------------------------
+
+nsIRDFService* nsXULContentUtils::gRDF;
+nsIDateTimeFormat* nsXULContentUtils::gFormat;
+nsICollation *nsXULContentUtils::gCollation;
+
+extern LazyLogModule gXULTemplateLog;
+
+#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
+//
+
+nsresult
+nsXULContentUtils::Init()
+{
+ static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+ nsresult rv = CallGetService(kRDFServiceCID, &gRDF);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+#define XUL_RESOURCE(ident, uri) \
+ PR_BEGIN_MACRO \
+ rv = gRDF->GetResource(NS_LITERAL_CSTRING(uri), &(ident)); \
+ if (NS_FAILED(rv)) return rv; \
+ PR_END_MACRO
+
+#define XUL_LITERAL(ident, val) \
+ PR_BEGIN_MACRO \
+ rv = gRDF->GetLiteral(val, &(ident)); \
+ if (NS_FAILED(rv)) return rv; \
+ PR_END_MACRO
+
+#include "nsXULResourceList.h"
+#undef XUL_RESOURCE
+#undef XUL_LITERAL
+
+ gFormat = nsIDateTimeFormat::Create().take();
+ if (!gFormat) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+nsXULContentUtils::Finish()
+{
+ NS_IF_RELEASE(gRDF);
+
+#define XUL_RESOURCE(ident, uri) NS_IF_RELEASE(ident)
+#define XUL_LITERAL(ident, val) NS_IF_RELEASE(ident)
+#include "nsXULResourceList.h"
+#undef XUL_RESOURCE
+#undef XUL_LITERAL
+
+ NS_IF_RELEASE(gFormat);
+ NS_IF_RELEASE(gCollation);
+
+ return NS_OK;
+}
+
+nsICollation*
+nsXULContentUtils::GetCollation()
+{
+ if (!gCollation) {
+ nsresult rv;
+
+ // get a locale service
+ nsCOMPtr<nsILocaleService> localeService =
+ do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsILocale> locale;
+ rv = localeService->GetApplicationLocale(getter_AddRefs(locale));
+ if (NS_SUCCEEDED(rv) && locale) {
+ nsCOMPtr<nsICollationFactory> colFactory =
+ do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
+ if (colFactory) {
+ rv = colFactory->CreateCollation(locale, &gCollation);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "couldn't create collation instance");
+ } else
+ NS_ERROR("couldn't create instance of collation factory");
+ } else
+ NS_ERROR("unable to get application locale");
+ } else
+ NS_ERROR("couldn't get locale factory");
+ }
+
+ return gCollation;
+}
+
+//------------------------------------------------------------------------
+
+nsresult
+nsXULContentUtils::FindChildByTag(nsIContent* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aTag,
+ nsIContent** aResult)
+{
+ for (nsIContent* child = aElement->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+
+ if (child->NodeInfo()->Equals(aTag, aNameSpaceID)) {
+ NS_ADDREF(*aResult = child);
+
+ return NS_OK;
+ }
+ }
+
+ *aResult = nullptr;
+ return NS_RDF_NO_VALUE; // not found
+}
+
+
+/*
+ Note: this routine is similar, yet distinctly different from, nsBookmarksService::GetTextForNode
+*/
+
+nsresult
+nsXULContentUtils::GetTextForNode(nsIRDFNode* aNode, nsAString& aResult)
+{
+ if (! aNode) {
+ aResult.Truncate();
+ return NS_OK;
+ }
+
+ nsresult rv;
+
+ // Literals are the most common, so try these first.
+ nsCOMPtr<nsIRDFLiteral> literal = do_QueryInterface(aNode);
+ if (literal) {
+ const char16_t* p;
+ rv = literal->GetValueConst(&p);
+ if (NS_FAILED(rv)) return rv;
+
+ aResult = p;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIRDFDate> dateLiteral = do_QueryInterface(aNode);
+ if (dateLiteral) {
+ PRTime value;
+ rv = dateLiteral->GetValue(&value);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoString str;
+ rv = gFormat->FormatPRTime(nullptr /* nsILocale* locale */,
+ kDateFormatShort,
+ kTimeFormatSeconds,
+ value,
+ str);
+ aResult.Assign(str);
+
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIRDFInt> intLiteral = do_QueryInterface(aNode);
+ if (intLiteral) {
+ int32_t value;
+ rv = intLiteral->GetValue(&value);
+ if (NS_FAILED(rv)) return rv;
+
+ aResult.Truncate();
+ nsAutoString intStr;
+ intStr.AppendInt(value, 10);
+ aResult.Append(intStr);
+ return NS_OK;
+ }
+
+
+ nsCOMPtr<nsIRDFResource> resource = do_QueryInterface(aNode);
+ if (resource) {
+ const char* p;
+ rv = resource->GetValueConst(&p);
+ if (NS_FAILED(rv)) return rv;
+ CopyUTF8toUTF16(p, aResult);
+ return NS_OK;
+ }
+
+ NS_ERROR("not a resource or a literal");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+nsXULContentUtils::GetResource(int32_t aNameSpaceID, nsIAtom* aAttribute, nsIRDFResource** aResult)
+{
+ // construct a fully-qualified URI from the namespace/tag pair.
+ NS_PRECONDITION(aAttribute != nullptr, "null ptr");
+ if (! aAttribute)
+ return NS_ERROR_NULL_POINTER;
+
+ return GetResource(aNameSpaceID, nsDependentAtomString(aAttribute),
+ aResult);
+}
+
+
+nsresult
+nsXULContentUtils::GetResource(int32_t aNameSpaceID, const nsAString& aAttribute, nsIRDFResource** aResult)
+{
+ // construct a fully-qualified URI from the namespace/tag pair.
+
+ // XXX should we allow nodes with no namespace???
+ //NS_PRECONDITION(aNameSpaceID != kNameSpaceID_Unknown, "no namespace");
+ //if (aNameSpaceID == kNameSpaceID_Unknown)
+ // return NS_ERROR_UNEXPECTED;
+
+ nsresult rv;
+
+ char16_t buf[256];
+ nsFixedString uri(buf, ArrayLength(buf), 0);
+ if (aNameSpaceID != kNameSpaceID_Unknown && aNameSpaceID != kNameSpaceID_None) {
+ rv = nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNameSpaceID, uri);
+ // XXX ignore failure; treat as "no namespace"
+ }
+
+ // XXX check to see if we need to insert a '/' or a '#'. Oy.
+ if (!uri.IsEmpty() && uri.Last() != '#' && uri.Last() != '/' && aAttribute.First() != '#')
+ uri.Append(char16_t('#'));
+
+ uri.Append(aAttribute);
+
+ rv = gRDF->GetUnicodeResource(uri, aResult);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get resource");
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+
+nsresult
+nsXULContentUtils::SetCommandUpdater(nsIDocument* aDocument, nsIContent* aElement)
+{
+ // Deal with setting up a 'commandupdater'. Pulls the 'events' and
+ // 'targets' attributes off of aElement, and adds it to the
+ // document's command dispatcher.
+ NS_PRECONDITION(aDocument != nullptr, "null ptr");
+ if (! aDocument)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aElement != nullptr, "null ptr");
+ if (! aElement)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIDOMXULDocument> xuldoc = do_QueryInterface(aDocument);
+ NS_ASSERTION(xuldoc != nullptr, "not a xul document");
+ if (! xuldoc)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIDOMXULCommandDispatcher> dispatcher;
+ rv = xuldoc->GetCommandDispatcher(getter_AddRefs(dispatcher));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get dispatcher");
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ASSERTION(dispatcher != nullptr, "no dispatcher");
+ if (! dispatcher)
+ return NS_ERROR_UNEXPECTED;
+
+ nsAutoString events;
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::events, events);
+ if (events.IsEmpty())
+ events.Assign('*');
+
+ nsAutoString targets;
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::targets, targets);
+
+ if (targets.IsEmpty())
+ targets.Assign('*');
+
+ nsCOMPtr<nsIDOMElement> domelement = do_QueryInterface(aElement);
+ NS_ASSERTION(domelement != nullptr, "not a DOM element");
+ 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());
+ MOZ_LOG(gXULTemplateLog, LogLevel::Info, ("Error parsing template: %s", aStr));
+ }
+}
diff --git a/dom/xul/templates/nsXULContentUtils.h b/dom/xul/templates/nsXULContentUtils.h
new file mode 100644
index 000000000..c106fd91e
--- /dev/null
+++ b/dom/xul/templates/nsXULContentUtils.h
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+
+ A package of routines shared by the XUL content code.
+
+ */
+
+#ifndef nsXULContentUtils_h__
+#define nsXULContentUtils_h__
+
+#include "nsISupports.h"
+
+class nsIAtom;
+class nsIContent;
+class nsIDocument;
+class nsIRDFNode;
+class nsIRDFResource;
+class nsIRDFLiteral;
+class nsIRDFService;
+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"
+#define ERROR_TEMPLATE_STORAGE_BAD_URI \
+ "only profile: or file URI are allowed"
+#define ERROR_TEMPLATE_STORAGE_CANNOT_OPEN_DATABASE \
+ "cannot open given database"
+#define ERROR_TEMPLATE_STORAGE_BAD_QUERY \
+ "syntax error in the SQL query"
+#define ERROR_TEMPLATE_STORAGE_UNKNOWN_QUERY_PARAMETER \
+ "the given named parameter is unknown in the SQL query"
+#define ERROR_TEMPLATE_STORAGE_WRONG_TYPE_QUERY_PARAMETER \
+ "the type of a query parameter is wrong"
+#define ERROR_TEMPLATE_STORAGE_QUERY_PARAMETER_NOT_BOUND \
+ "a query parameter cannot be bound to the SQL query"
+
+class nsXULContentUtils
+{
+protected:
+ static nsIRDFService* gRDF;
+ static nsIDateTimeFormat* gFormat;
+ static nsICollation *gCollation;
+
+ static bool gDisableXULCache;
+
+ static int
+ DisableXULCacheChangedCallback(const char* aPrefName, void* aClosure);
+
+public:
+ static nsresult
+ Init();
+
+ static nsresult
+ Finish();
+
+ static nsresult
+ FindChildByTag(nsIContent *aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aTag,
+ nsIContent **aResult);
+
+ static nsresult
+ FindChildByResource(nsIContent* aElement,
+ nsIRDFResource* aResource,
+ nsIContent** aResult);
+
+ static nsresult
+ GetTextForNode(nsIRDFNode* aNode, nsAString& aResult);
+
+ static nsresult
+ GetResource(int32_t aNameSpaceID, nsIAtom* aAttribute, nsIRDFResource** aResult);
+
+ static nsresult
+ GetResource(int32_t 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();
+
+#define XUL_RESOURCE(ident, uri) static nsIRDFResource* ident
+#define XUL_LITERAL(ident, val) static nsIRDFLiteral* ident
+#include "nsXULResourceList.h"
+#undef XUL_RESOURCE
+#undef XUL_LITERAL
+};
+
+#endif // nsXULContentUtils_h__
diff --git a/dom/xul/templates/nsXULResourceList.h b/dom/xul/templates/nsXULResourceList.h
new file mode 100644
index 000000000..eaea7e996
--- /dev/null
+++ b/dom/xul/templates/nsXULResourceList.h
@@ -0,0 +1,13 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// N.B., no include guard! We'll include this multiple times in some
+// files.
+
+XUL_RESOURCE(NC_child, NC_NAMESPACE_URI "child");
+XUL_RESOURCE(NC_Folder, NC_NAMESPACE_URI "Folder");
+XUL_RESOURCE(NC_open, NC_NAMESPACE_URI "open");
+
+XUL_LITERAL(true_, u"true");
diff --git a/dom/xul/templates/nsXULSortService.cpp b/dom/xul/templates/nsXULSortService.cpp
new file mode 100644
index 000000000..ab3e13461
--- /dev/null
+++ b/dom/xul/templates/nsXULSortService.cpp
@@ -0,0 +1,507 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Original Code has been modified by IBM Corporation.
+ * Modifications made by IBM described herein are
+ * Copyright (c) International Business Machines
+ * Corporation, 2000
+ *
+ * Modifications to Mozilla code or documentation
+ * identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink
+ * use in OS2
+ */
+
+/*
+ This file provides the implementation for the sort service manager.
+ */
+
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMNode.h"
+#include "nsIServiceManager.h"
+#include "nsGkAtoms.h"
+#include "nsNameSpaceManager.h"
+#include "nsXULContentUtils.h"
+#include "nsString.h"
+#include "nsQuickSort.h"
+#include "nsWhitespaceTokenizer.h"
+#include "nsXULSortService.h"
+#include "nsIDOMXULElement.h"
+#include "nsIXULTemplateBuilder.h"
+#include "nsTemplateMatch.h"
+#include "nsICollation.h"
+#include "nsUnicharUtils.h"
+
+NS_IMPL_ISUPPORTS(XULSortServiceImpl, nsIXULSortService)
+
+void
+XULSortServiceImpl::SetSortHints(nsIContent *aNode, nsSortState* aSortState)
+{
+ // set sort and sortDirection attributes when is sort is done
+ aNode->SetAttr(kNameSpaceID_None, nsGkAtoms::sort,
+ aSortState->sort, true);
+
+ nsAutoString direction;
+ if (aSortState->direction == nsSortState_descending)
+ direction.AssignLiteral("descending");
+ else if (aSortState->direction == nsSortState_ascending)
+ direction.AssignLiteral("ascending");
+ aNode->SetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection,
+ direction, true);
+
+ // for trees, also set the sort info on the currently sorted column
+ if (aNode->NodeInfo()->Equals(nsGkAtoms::tree, kNameSpaceID_XUL)) {
+ if (aSortState->sortKeys.Count() >= 1) {
+ nsAutoString sortkey;
+ aSortState->sortKeys[0]->ToString(sortkey);
+ SetSortColumnHints(aNode, sortkey, direction);
+ }
+ }
+}
+
+void
+XULSortServiceImpl::SetSortColumnHints(nsIContent *content,
+ const nsAString &sortResource,
+ const nsAString &sortDirection)
+{
+ // set sort info on current column. This ensures that the
+ // column header sort indicator is updated properly.
+ for (nsIContent* child = content->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child->IsXULElement(nsGkAtoms::treecols)) {
+ SetSortColumnHints(child, sortResource, sortDirection);
+ } else if (child->IsXULElement(nsGkAtoms::treecol)) {
+ nsAutoString value;
+ child->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, value);
+ // also check the resource attribute for older code
+ if (value.IsEmpty())
+ child->GetAttr(kNameSpaceID_None, nsGkAtoms::resource, value);
+ if (value == sortResource) {
+ child->SetAttr(kNameSpaceID_None, nsGkAtoms::sortActive,
+ NS_LITERAL_STRING("true"), true);
+ child->SetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection,
+ sortDirection, true);
+ // Note: don't break out of loop; want to set/unset
+ // attribs on ALL sort columns
+ } else if (!value.IsEmpty()) {
+ child->UnsetAttr(kNameSpaceID_None, nsGkAtoms::sortActive,
+ true);
+ child->UnsetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection,
+ true);
+ }
+ }
+ }
+}
+
+nsresult
+XULSortServiceImpl::GetItemsToSort(nsIContent *aContainer,
+ nsSortState* aSortState,
+ nsTArray<contentSortInfo>& aSortItems)
+{
+ // if there is a template attached to the sort node, use the builder to get
+ // the items to be sorted
+ nsCOMPtr<nsIDOMXULElement> element = do_QueryInterface(aContainer);
+ if (element) {
+ nsCOMPtr<nsIXULTemplateBuilder> builder;
+ element->GetBuilder(getter_AddRefs(builder));
+
+ if (builder) {
+ nsresult rv = builder->GetQueryProcessor(getter_AddRefs(aSortState->processor));
+ if (NS_FAILED(rv) || !aSortState->processor)
+ return rv;
+
+ return GetTemplateItemsToSort(aContainer, builder, aSortState, aSortItems);
+ }
+ }
+
+ // if there is no template builder, just get the children. For trees,
+ // get the treechildren element as use that as the parent
+ nsCOMPtr<nsIContent> treechildren;
+ if (aContainer->NodeInfo()->Equals(nsGkAtoms::tree, kNameSpaceID_XUL)) {
+ nsXULContentUtils::FindChildByTag(aContainer,
+ kNameSpaceID_XUL,
+ nsGkAtoms::treechildren,
+ getter_AddRefs(treechildren));
+ if (!treechildren)
+ return NS_OK;
+
+ aContainer = treechildren;
+ }
+
+ for (nsIContent* child = aContainer->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ contentSortInfo* cinfo = aSortItems.AppendElement();
+ if (!cinfo)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ cinfo->content = child;
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+XULSortServiceImpl::GetTemplateItemsToSort(nsIContent* aContainer,
+ nsIXULTemplateBuilder* aBuilder,
+ nsSortState* aSortState,
+ nsTArray<contentSortInfo>& aSortItems)
+{
+ for (nsIContent* child = aContainer->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+
+ nsCOMPtr<nsIDOMElement> childnode = do_QueryInterface(child);
+
+ nsCOMPtr<nsIXULTemplateResult> result;
+ nsresult rv = aBuilder->GetResultForContent(childnode, getter_AddRefs(result));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (result) {
+ contentSortInfo* cinfo = aSortItems.AppendElement();
+ if (!cinfo)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ cinfo->content = child;
+ cinfo->result = result;
+ }
+ else if (!aContainer->IsXULElement(nsGkAtoms::_template)) {
+ rv = GetTemplateItemsToSort(child, aBuilder, aSortState, aSortItems);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+int
+testSortCallback(const void *data1, const void *data2, void *privateData)
+{
+ /// Note: testSortCallback is a small C callback stub for NS_QuickSort
+ contentSortInfo *left = (contentSortInfo *)data1;
+ contentSortInfo *right = (contentSortInfo *)data2;
+ nsSortState* sortState = (nsSortState *)privateData;
+
+ int32_t sortOrder = 0;
+
+ if (sortState->direction == nsSortState_natural && sortState->processor) {
+ // sort in natural order
+ sortState->processor->CompareResults(left->result, right->result,
+ nullptr, sortState->sortHints, &sortOrder);
+ }
+ else {
+ int32_t length = sortState->sortKeys.Count();
+ for (int32_t t = 0; t < length; t++) {
+ // for templates, use the query processor to do sorting
+ if (sortState->processor) {
+ sortState->processor->CompareResults(left->result, right->result,
+ sortState->sortKeys[t],
+ sortState->sortHints, &sortOrder);
+ if (sortOrder)
+ break;
+ }
+ else {
+ // no template, so just compare attributes. Ignore namespaces for now.
+ nsAutoString leftstr, rightstr;
+ left->content->GetAttr(kNameSpaceID_None, sortState->sortKeys[t], leftstr);
+ right->content->GetAttr(kNameSpaceID_None, sortState->sortKeys[t], rightstr);
+
+ sortOrder = XULSortServiceImpl::CompareValues(leftstr, rightstr, sortState->sortHints);
+ }
+ }
+ }
+
+ if (sortState->direction == nsSortState_descending)
+ sortOrder = -sortOrder;
+
+ return sortOrder;
+}
+
+nsresult
+XULSortServiceImpl::SortContainer(nsIContent *aContainer, nsSortState* aSortState)
+{
+ nsTArray<contentSortInfo> items;
+ nsresult rv = GetItemsToSort(aContainer, aSortState, items);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numResults = items.Length();
+ if (!numResults)
+ return NS_OK;
+
+ uint32_t i;
+
+ // inbetweenSeparatorSort sorts the items between separators independently
+ if (aSortState->inbetweenSeparatorSort) {
+ uint32_t startIndex = 0;
+ for (i = 0; i < numResults; i++) {
+ if (i > startIndex + 1) {
+ nsAutoString type;
+ items[i].result->GetType(type);
+ if (type.EqualsLiteral("separator")) {
+ if (aSortState->invertSort)
+ InvertSortInfo(items, startIndex, i - startIndex);
+ else
+ NS_QuickSort((void *)(items.Elements() + startIndex), i - startIndex,
+ sizeof(contentSortInfo), testSortCallback, (void*)aSortState);
+
+ startIndex = i + 1;
+ }
+ }
+ }
+
+ if (i > startIndex + 1) {
+ if (aSortState->invertSort)
+ InvertSortInfo(items, startIndex, i - startIndex);
+ else
+ NS_QuickSort((void *)(items.Elements() + startIndex), i - startIndex,
+ sizeof(contentSortInfo), testSortCallback, (void*)aSortState);
+ }
+ } else {
+ // if the items are just being inverted, that is, just switching between
+ // ascending and descending, just reverse the list.
+ if (aSortState->invertSort)
+ InvertSortInfo(items, 0, numResults);
+ else
+ NS_QuickSort((void *)items.Elements(), numResults,
+ sizeof(contentSortInfo), testSortCallback, (void*)aSortState);
+ }
+
+ // first remove the items from the old positions
+ for (i = 0; i < numResults; i++) {
+ nsIContent* child = items[i].content;
+ nsIContent* parent = child->GetParent();
+
+ if (parent) {
+ // remember the parent so that it can be reinserted back
+ // into the same parent. This is necessary as multiple rules
+ // may generate results which get placed in different locations.
+ items[i].parent = parent;
+ int32_t index = parent->IndexOf(child);
+ parent->RemoveChildAt(index, true);
+ }
+ }
+
+ // now add the items back in sorted order
+ for (i = 0; i < numResults; i++)
+ {
+ nsIContent* child = items[i].content;
+ nsIContent* parent = items[i].parent;
+ if (parent) {
+ parent->AppendChildTo(child, true);
+
+ // if it's a container in a tree or menu, find its children,
+ // and sort those also
+ if (!child->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container,
+ nsGkAtoms::_true, eCaseMatters))
+ continue;
+
+ for (nsIContent* grandchild = child->GetFirstChild();
+ grandchild;
+ grandchild = grandchild->GetNextSibling()) {
+ mozilla::dom::NodeInfo *ni = grandchild->NodeInfo();
+ nsIAtom *localName = ni->NameAtom();
+ if (ni->NamespaceID() == kNameSpaceID_XUL &&
+ (localName == nsGkAtoms::treechildren ||
+ localName == nsGkAtoms::menupopup)) {
+ SortContainer(grandchild, aSortState);
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+XULSortServiceImpl::InvertSortInfo(nsTArray<contentSortInfo>& aData,
+ int32_t aStart, int32_t aNumItems)
+{
+ if (aNumItems > 1) {
+ // reverse the items in the array starting from aStart
+ int32_t upPoint = (aNumItems + 1) / 2 + aStart;
+ int32_t downPoint = (aNumItems - 2) / 2 + aStart;
+ int32_t half = aNumItems / 2;
+ while (half-- > 0) {
+ aData[downPoint--].swap(aData[upPoint++]);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+XULSortServiceImpl::InitializeSortState(nsIContent* aRootElement,
+ nsIContent* aContainer,
+ const nsAString& aSortKey,
+ const nsAString& aSortHints,
+ nsSortState* aSortState)
+{
+ // used as an optimization for the content builder
+ if (aContainer != aSortState->lastContainer.get()) {
+ aSortState->lastContainer = aContainer;
+ aSortState->lastWasFirst = false;
+ aSortState->lastWasLast = false;
+ }
+
+ // The attributes allowed are either:
+ // sort="key1 key2 ..."
+ // or sortResource="key1" sortResource2="key2"
+ // The latter is for backwards compatibility, and is equivalent to concatenating
+ // both values in the sort attribute
+ nsAutoString sort(aSortKey);
+ aSortState->sortKeys.Clear();
+ if (sort.IsEmpty()) {
+ nsAutoString sortResource, sortResource2;
+ aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sortResource, sortResource);
+ if (!sortResource.IsEmpty()) {
+ nsCOMPtr<nsIAtom> sortkeyatom = NS_Atomize(sortResource);
+ aSortState->sortKeys.AppendObject(sortkeyatom);
+ sort.Append(sortResource);
+
+ aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sortResource2, sortResource2);
+ if (!sortResource2.IsEmpty()) {
+ nsCOMPtr<nsIAtom> sortkeyatom2 = NS_Atomize(sortResource2);
+ aSortState->sortKeys.AppendObject(sortkeyatom2);
+ sort.Append(' ');
+ sort.Append(sortResource2);
+ }
+ }
+ }
+ else {
+ nsWhitespaceTokenizer tokenizer(sort);
+ while (tokenizer.hasMoreTokens()) {
+ nsCOMPtr<nsIAtom> keyatom = NS_Atomize(tokenizer.nextToken());
+ NS_ENSURE_TRUE(keyatom, NS_ERROR_OUT_OF_MEMORY);
+ aSortState->sortKeys.AppendObject(keyatom);
+ }
+ }
+
+ aSortState->sort.Assign(sort);
+ aSortState->direction = nsSortState_natural;
+
+ bool noNaturalState = false;
+ nsWhitespaceTokenizer tokenizer(aSortHints);
+ while (tokenizer.hasMoreTokens()) {
+ const nsDependentSubstring& token(tokenizer.nextToken());
+ if (token.EqualsLiteral("comparecase"))
+ aSortState->sortHints |= nsIXULSortService::SORT_COMPARECASE;
+ else if (token.EqualsLiteral("integer"))
+ aSortState->sortHints |= nsIXULSortService::SORT_INTEGER;
+ else if (token.EqualsLiteral("descending"))
+ aSortState->direction = nsSortState_descending;
+ else if (token.EqualsLiteral("ascending"))
+ aSortState->direction = nsSortState_ascending;
+ else if (token.EqualsLiteral("twostate"))
+ noNaturalState = true;
+ }
+
+ // if the twostate flag was set, the natural order is skipped and only
+ // ascending and descending are allowed
+ if (aSortState->direction == nsSortState_natural && noNaturalState) {
+ aSortState->direction = nsSortState_ascending;
+ }
+
+ // set up sort order info
+ aSortState->invertSort = false;
+
+ nsAutoString existingsort;
+ aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, existingsort);
+ nsAutoString existingsortDirection;
+ aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, existingsortDirection);
+
+ // if just switching direction, set the invertSort flag
+ if (sort.Equals(existingsort)) {
+ if (aSortState->direction == nsSortState_descending) {
+ if (existingsortDirection.EqualsLiteral("ascending"))
+ aSortState->invertSort = true;
+ }
+ else if (aSortState->direction == nsSortState_ascending &&
+ existingsortDirection.EqualsLiteral("descending")) {
+ aSortState->invertSort = true;
+ }
+ }
+
+ // sort items between separators independently
+ aSortState->inbetweenSeparatorSort =
+ aRootElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::sortSeparators,
+ nsGkAtoms::_true, eCaseMatters);
+
+ // sort static content (non template generated nodes) after generated content
+ aSortState->sortStaticsLast = aRootElement->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::sortStaticsLast,
+ nsGkAtoms::_true, eCaseMatters);
+
+ aSortState->initialized = true;
+
+ return NS_OK;
+}
+
+int32_t
+XULSortServiceImpl::CompareValues(const nsAString& aLeft,
+ const nsAString& aRight,
+ uint32_t aSortHints)
+{
+ if (aSortHints & SORT_INTEGER) {
+ nsresult err;
+ int32_t leftint = PromiseFlatString(aLeft).ToInteger(&err);
+ if (NS_SUCCEEDED(err)) {
+ int32_t rightint = PromiseFlatString(aRight).ToInteger(&err);
+ if (NS_SUCCEEDED(err)) {
+ return leftint - rightint;
+ }
+ }
+ // if they aren't integers, just fall through and compare strings
+ }
+
+ if (aSortHints & SORT_COMPARECASE) {
+ return ::Compare(aLeft, aRight);
+ }
+
+ nsICollation* collation = nsXULContentUtils::GetCollation();
+ if (collation) {
+ int32_t result;
+ collation->CompareString(nsICollation::kCollationCaseInSensitive,
+ aLeft, aRight, &result);
+ return result;
+ }
+
+ return ::Compare(aLeft, aRight, nsCaseInsensitiveStringComparator());
+}
+
+NS_IMETHODIMP
+XULSortServiceImpl::Sort(nsIDOMNode* aNode,
+ const nsAString& aSortKey,
+ const nsAString& aSortHints)
+{
+ // get root content node
+ nsCOMPtr<nsIContent> sortNode = do_QueryInterface(aNode);
+ if (!sortNode)
+ return NS_ERROR_FAILURE;
+
+ nsSortState sortState;
+ nsresult rv = InitializeSortState(sortNode, sortNode,
+ aSortKey, aSortHints, &sortState);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // store sort info in attributes on content
+ SetSortHints(sortNode, &sortState);
+ rv = SortContainer(sortNode, &sortState);
+
+ sortState.processor = nullptr; // don't hang on to this reference
+ return rv;
+}
+
+nsresult
+NS_NewXULSortService(nsIXULSortService** sortService)
+{
+ *sortService = new XULSortServiceImpl();
+ NS_ADDREF(*sortService);
+ return NS_OK;
+}
diff --git a/dom/xul/templates/nsXULSortService.h b/dom/xul/templates/nsXULSortService.h
new file mode 100644
index 000000000..306481e0d
--- /dev/null
+++ b/dom/xul/templates/nsXULSortService.h
@@ -0,0 +1,187 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Original Code has been modified by IBM Corporation.
+ * Modifications made by IBM described herein are
+ * Copyright (c) International Business Machines
+ * Corporation, 2000
+ *
+ * Modifications to Mozilla code or documentation
+ * identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink
+ * use in OS2
+ */
+
+/*
+ This sort service is used to sort template built content or content by attribute.
+ */
+
+#ifndef nsXULTemplateResultSetRDF_h
+#define nsXULTemplateResultSetRDF_h
+
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsTArray.h"
+#include "nsIContent.h"
+#include "nsIXULTemplateResult.h"
+#include "nsIXULTemplateQueryProcessor.h"
+#include "nsIXULSortService.h"
+#include "nsCycleCollectionParticipant.h"
+
+enum nsSortState_direction {
+ nsSortState_descending,
+ nsSortState_ascending,
+ nsSortState_natural
+};
+
+// the sort state holds info about the current sort
+struct nsSortState
+{
+ bool initialized;
+ MOZ_INIT_OUTSIDE_CTOR bool invertSort;
+ MOZ_INIT_OUTSIDE_CTOR bool inbetweenSeparatorSort;
+ MOZ_INIT_OUTSIDE_CTOR bool sortStaticsLast;
+ MOZ_INIT_OUTSIDE_CTOR bool isContainerRDFSeq;
+
+ uint32_t sortHints;
+
+ MOZ_INIT_OUTSIDE_CTOR nsSortState_direction direction;
+ nsAutoString sort;
+ nsCOMArray<nsIAtom> sortKeys;
+
+ nsCOMPtr<nsIXULTemplateQueryProcessor> processor;
+ nsCOMPtr<nsIContent> lastContainer;
+ MOZ_INIT_OUTSIDE_CTOR bool lastWasFirst, lastWasLast;
+
+ nsSortState()
+ : initialized(false),
+ isContainerRDFSeq(false),
+ sortHints(0)
+ {
+ }
+ void Traverse(nsCycleCollectionTraversalCallback &cb) const
+ {
+ cb.NoteXPCOMChild(processor);
+ cb.NoteXPCOMChild(lastContainer);
+ }
+};
+
+// information about a particular item to be sorted
+struct contentSortInfo {
+ nsCOMPtr<nsIContent> content;
+ nsCOMPtr<nsIContent> parent;
+ nsCOMPtr<nsIXULTemplateResult> result;
+ void swap(contentSortInfo& other)
+ {
+ content.swap(other.content);
+ parent.swap(other.parent);
+ result.swap(other.result);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////
+// ServiceImpl
+//
+// This is the sort service.
+//
+class XULSortServiceImpl : public nsIXULSortService
+{
+protected:
+ XULSortServiceImpl(void) {}
+ virtual ~XULSortServiceImpl(void) {}
+
+ friend nsresult NS_NewXULSortService(nsIXULSortService** mgr);
+
+private:
+
+public:
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ // nsISortService
+ NS_DECL_NSIXULSORTSERVICE
+
+ /**
+ * Set sort and sortDirection attributes when a sort is done.
+ */
+ void
+ SetSortHints(nsIContent *aNode, nsSortState* aSortState);
+
+ /**
+ * Set sortActive and sortDirection attributes on a tree column when a sort
+ * is done. The column to change is the one with a sort attribute that
+ * matches the sort key. The sort attributes are removed from the other
+ * columns.
+ */
+ void
+ SetSortColumnHints(nsIContent *content,
+ const nsAString &sortResource,
+ const nsAString &sortDirection);
+
+ /**
+ * Determine the list of items which need to be sorted. This is determined
+ * in the following way:
+ * - for elements that have a content builder, get its list of generated
+ * results
+ * - otherwise, for trees, get the child treeitems
+ * - otherwise, get the direct children
+ */
+ nsresult
+ GetItemsToSort(nsIContent *aContainer,
+ nsSortState* aSortState,
+ nsTArray<contentSortInfo>& aSortItems);
+
+ /**
+ * Get the list of items to sort for template built content
+ */
+ nsresult
+ GetTemplateItemsToSort(nsIContent* aContainer,
+ nsIXULTemplateBuilder* aBuilder,
+ nsSortState* aSortState,
+ nsTArray<contentSortInfo>& aSortItems);
+
+ /**
+ * Sort a container using the supplied sort state details.
+ */
+ nsresult
+ SortContainer(nsIContent *aContainer, nsSortState* aSortState);
+
+ /**
+ * Given a list of sortable items, reverse the list. This is done
+ * when simply changing the sort direction for the same key.
+ */
+ nsresult
+ InvertSortInfo(nsTArray<contentSortInfo>& aData,
+ int32_t aStart, int32_t aNumItems);
+
+ /**
+ * Initialize sort information from attributes specified on the container,
+ * the sort key and sort direction.
+ *
+ * @param aRootElement the element that contains sort attributes
+ * @param aContainer the container to sort, usually equal to aRootElement
+ * @param aSortKey space separated list of sort keys
+ * @param aSortDirection direction to sort in
+ * @param aSortState structure filled in with sort data
+ */
+ static nsresult
+ InitializeSortState(nsIContent* aRootElement,
+ nsIContent* aContainer,
+ const nsAString& aSortKey,
+ const nsAString& aSortDirection,
+ nsSortState* aSortState);
+
+ /**
+ * Compares aLeft and aRight and returns < 0, 0, or > 0. The sort
+ * hints are checked for case matching and integer sorting.
+ */
+ static int32_t CompareValues(const nsAString& aLeft,
+ const nsAString& aRight,
+ uint32_t aSortHints);
+};
+
+#endif
diff --git a/dom/xul/templates/nsXULTemplateBuilder.cpp b/dom/xul/templates/nsXULTemplateBuilder.cpp
new file mode 100644
index 000000000..49fb3335d
--- /dev/null
+++ b/dom/xul/templates/nsXULTemplateBuilder.cpp
@@ -0,0 +1,2573 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+
+ Builds content from a datasource using the XUL <template> tag.
+
+ TO DO
+
+ . Fix ContentTagTest's location in the network construction
+
+ To turn on logging for this module, set:
+
+ MOZ_LOG=nsXULTemplateBuilder:5
+
+ */
+
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsIContent.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMXULElement.h"
+#include "nsIDocument.h"
+#include "nsBindingManager.h"
+#include "nsIDOMNodeList.h"
+#include "nsIObserverService.h"
+#include "nsIRDFCompositeDataSource.h"
+#include "nsIRDFInferDataSource.h"
+#include "nsIRDFContainerUtils.h"
+#include "nsIXULDocument.h"
+#include "nsIXULTemplateBuilder.h"
+#include "nsIXULBuilderListener.h"
+#include "nsIRDFRemoteDataSource.h"
+#include "nsIRDFService.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIServiceManager.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIMutableArray.h"
+#include "nsIURL.h"
+#include "nsIXPConnect.h"
+#include "nsContentCID.h"
+#include "nsNameSpaceManager.h"
+#include "nsRDFCID.h"
+#include "nsXULContentUtils.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsXPIDLString.h"
+#include "nsWhitespaceTokenizer.h"
+#include "nsGkAtoms.h"
+#include "nsXULElement.h"
+#include "jsapi.h"
+#include "mozilla/Logging.h"
+#include "rdf.h"
+#include "PLDHashTable.h"
+#include "plhash.h"
+#include "nsDOMClassInfoID.h"
+#include "nsPIDOMWindow.h"
+#include "nsIConsoleService.h"
+#include "nsNetUtil.h"
+#include "nsXULTemplateBuilder.h"
+#include "nsXULTemplateQueryProcessorRDF.h"
+#include "nsXULTemplateQueryProcessorXML.h"
+#include "nsXULTemplateQueryProcessorStorage.h"
+#include "nsContentUtils.h"
+#include "ChildIterator.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsGlobalWindow.h"
+
+using namespace mozilla::dom;
+using namespace mozilla;
+
+//----------------------------------------------------------------------
+//
+// nsXULTemplateBuilder
+//
+
+nsrefcnt nsXULTemplateBuilder::gRefCnt = 0;
+nsIRDFService* nsXULTemplateBuilder::gRDFService;
+nsIRDFContainerUtils* nsXULTemplateBuilder::gRDFContainerUtils;
+nsIScriptSecurityManager* nsXULTemplateBuilder::gScriptSecurityManager;
+nsIPrincipal* nsXULTemplateBuilder::gSystemPrincipal;
+nsIObserverService* nsXULTemplateBuilder::gObserverService;
+
+LazyLogModule gXULTemplateLog("nsXULTemplateBuilder");
+
+#define NS_QUERY_PROCESSOR_CONTRACTID_PREFIX "@mozilla.org/xul/xul-query-processor;1?name="
+
+//----------------------------------------------------------------------
+//
+// nsXULTemplateBuilder methods
+//
+
+nsXULTemplateBuilder::nsXULTemplateBuilder(void)
+ : mQueriesCompiled(false),
+ mFlags(0),
+ mTop(nullptr),
+ mObservedDocument(nullptr)
+{
+ MOZ_COUNT_CTOR(nsXULTemplateBuilder);
+}
+
+void
+nsXULTemplateBuilder::DestroyMatchMap()
+{
+ for (auto iter = mMatchMap.Iter(); !iter.Done(); iter.Next()) {
+ nsTemplateMatch*& match = iter.Data();
+ // delete all the matches in the list
+ while (match) {
+ nsTemplateMatch* next = match->mNext;
+ nsTemplateMatch::Destroy(match, true);
+ match = next;
+ }
+
+ iter.Remove();
+ }
+}
+
+nsXULTemplateBuilder::~nsXULTemplateBuilder(void)
+{
+ Uninit(true);
+
+ if (--gRefCnt == 0) {
+ NS_IF_RELEASE(gRDFService);
+ NS_IF_RELEASE(gRDFContainerUtils);
+ NS_IF_RELEASE(gSystemPrincipal);
+ NS_IF_RELEASE(gScriptSecurityManager);
+ NS_IF_RELEASE(gObserverService);
+ }
+
+ MOZ_COUNT_DTOR(nsXULTemplateBuilder);
+}
+
+
+nsresult
+nsXULTemplateBuilder::InitGlobals()
+{
+ nsresult rv;
+
+ if (gRefCnt++ == 0) {
+ // Initialize the global shared reference to the service
+ // manager and get some shared resource objects.
+ NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+ rv = CallGetService(kRDFServiceCID, &gRDFService);
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID);
+ rv = CallGetService(kRDFContainerUtilsCID, &gRDFContainerUtils);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = CallGetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID,
+ &gScriptSecurityManager);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = gScriptSecurityManager->GetSystemPrincipal(&gSystemPrincipal);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = CallGetService(NS_OBSERVERSERVICE_CONTRACTID, &gObserverService);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+nsXULTemplateBuilder::StartObserving(nsIDocument* aDocument)
+{
+ aDocument->AddObserver(this);
+ mObservedDocument = aDocument;
+ gObserverService->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, false);
+}
+
+void
+nsXULTemplateBuilder::StopObserving()
+{
+ MOZ_ASSERT(mObservedDocument);
+ mObservedDocument->RemoveObserver(this);
+ mObservedDocument = nullptr;
+ gObserverService->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
+}
+
+void
+nsXULTemplateBuilder::CleanUp(bool aIsFinal)
+{
+ for (int32_t q = mQuerySets.Length() - 1; q >= 0; q--) {
+ nsTemplateQuerySet* qs = mQuerySets[q];
+ delete qs;
+ }
+
+ mQuerySets.Clear();
+
+ DestroyMatchMap();
+
+ // Setting mQueryProcessor to null will close connections. This would be
+ // handled by the cycle collector, but we want to close them earlier.
+ if (aIsFinal)
+ mQueryProcessor = nullptr;
+}
+
+void
+nsXULTemplateBuilder::Uninit(bool aIsFinal)
+{
+ if (mObservedDocument && aIsFinal) {
+ StopObserving();
+ }
+
+ if (mQueryProcessor)
+ mQueryProcessor->Done();
+
+ CleanUp(aIsFinal);
+
+ mRootResult = nullptr;
+ mRefVariable = nullptr;
+ mMemberVariable = nullptr;
+
+ mQueriesCompiled = false;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULTemplateBuilder)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULTemplateBuilder)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDataSource)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDB)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCompDB)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoot)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootResult)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mListeners)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mQueryProcessor)
+ tmp->DestroyMatchMap();
+ for (uint32_t i = 0; i < tmp->mQuerySets.Length(); ++i) {
+ nsTemplateQuerySet* qs = tmp->mQuerySets[i];
+ delete qs;
+ }
+ tmp->mQuerySets.Clear();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULTemplateBuilder)
+ if (tmp->mObservedDocument && !cb.WantAllTraces()) {
+ // The global observer service holds us alive.
+ return NS_SUCCESS_INTERRUPTED_TRAVERSE;
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDataSource)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDB)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCompDB)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootResult)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListeners)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueryProcessor)
+
+ for (auto iter = tmp->mMatchMap.Iter(); !iter.Done(); iter.Next()) {
+ cb.NoteXPCOMChild(iter.Key());
+ nsTemplateMatch* match = iter.UserData();
+ while (match) {
+ cb.NoteXPCOMChild(match->GetContainer());
+ cb.NoteXPCOMChild(match->mResult);
+ match = match->mNext;
+ }
+ }
+
+ {
+ uint32_t i, count = tmp->mQuerySets.Length();
+ for (i = 0; i < count; ++i) {
+ nsTemplateQuerySet *set = tmp->mQuerySets[i];
+ cb.NoteXPCOMChild(set->mQueryNode);
+ cb.NoteXPCOMChild(set->mCompiledQuery);
+ uint16_t j, rulesCount = set->RuleCount();
+ for (j = 0; j < rulesCount; ++j) {
+ set->GetRuleAt(j)->Traverse(cb);
+ }
+ }
+ }
+ tmp->Traverse(cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULTemplateBuilder)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULTemplateBuilder)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULTemplateBuilder)
+ NS_INTERFACE_MAP_ENTRY(nsIXULTemplateBuilder)
+ NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXULTemplateBuilder)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(XULTemplateBuilder)
+NS_INTERFACE_MAP_END
+
+//----------------------------------------------------------------------
+//
+// nsIXULTemplateBuilder methods
+//
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::GetRoot(nsIDOMElement** aResult)
+{
+ if (mRoot) {
+ return CallQueryInterface(mRoot, aResult);
+ }
+ *aResult = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::GetDatasource(nsISupports** aResult)
+{
+ if (mCompDB)
+ NS_ADDREF(*aResult = mCompDB);
+ else
+ NS_IF_ADDREF(*aResult = mDataSource);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::SetDatasource(nsISupports* aResult)
+{
+ mDataSource = aResult;
+ mCompDB = do_QueryInterface(mDataSource);
+
+ return Rebuild();
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::GetDatabase(nsIRDFCompositeDataSource** aResult)
+{
+ NS_IF_ADDREF(*aResult = mCompDB);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::GetQueryProcessor(nsIXULTemplateQueryProcessor** aResult)
+{
+ NS_IF_ADDREF(*aResult = mQueryProcessor.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::AddRuleFilter(nsIDOMNode* aRule, nsIXULTemplateRuleFilter* aFilter)
+{
+ if (!aRule || !aFilter)
+ return NS_ERROR_NULL_POINTER;
+
+ // a custom rule filter may be added, one for each rule. If a new one is
+ // added, it replaces the old one. Look for the right rule and set its
+ // filter
+
+ int32_t count = mQuerySets.Length();
+ for (int32_t q = 0; q < count; q++) {
+ nsTemplateQuerySet* queryset = mQuerySets[q];
+
+ int16_t rulecount = queryset->RuleCount();
+ for (int16_t r = 0; r < rulecount; r++) {
+ nsTemplateRule* rule = queryset->GetRuleAt(r);
+
+ nsCOMPtr<nsIDOMNode> rulenode;
+ rule->GetRuleNode(getter_AddRefs(rulenode));
+ if (aRule == rulenode) {
+ rule->SetRuleFilter(aFilter);
+ return NS_OK;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::Rebuild()
+{
+ int32_t i;
+
+ for (i = mListeners.Count() - 1; i >= 0; --i) {
+ mListeners[i]->WillRebuild(this);
+ }
+
+ nsresult rv = RebuildAll();
+
+ for (i = mListeners.Count() - 1; i >= 0; --i) {
+ mListeners[i]->DidRebuild(this);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::Refresh()
+{
+ nsresult rv;
+
+ if (!mCompDB)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsISimpleEnumerator> dslist;
+ rv = mCompDB->GetDataSources(getter_AddRefs(dslist));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ nsCOMPtr<nsISupports> next;
+ nsCOMPtr<nsIRDFRemoteDataSource> rds;
+
+ while(NS_SUCCEEDED(dslist->HasMoreElements(&hasMore)) && hasMore) {
+ dslist->GetNext(getter_AddRefs(next));
+ if (next && (rds = do_QueryInterface(next))) {
+ rds->Refresh(false);
+ }
+ }
+
+ // XXXbsmedberg: it would be kinda nice to install an async nsIRDFXMLSink
+ // observer and call rebuild() once the load is complete. See bug 254600.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::Init(nsIContent* aElement)
+{
+ NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER);
+ mRoot = aElement;
+
+ nsCOMPtr<nsIDocument> doc = mRoot->GetComposedDoc();
+ NS_ASSERTION(doc, "element has no document");
+ if (! doc)
+ return NS_ERROR_UNEXPECTED;
+
+ bool shouldDelay;
+ nsresult rv = LoadDataSources(doc, &shouldDelay);
+
+ if (NS_SUCCEEDED(rv)) {
+ StartObserving(doc);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::CreateContents(nsIContent* aElement, bool aForceCreation)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::HasGeneratedContent(nsIRDFResource* aResource,
+ nsIAtom* aTag,
+ bool* aGenerated)
+{
+ *aGenerated = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::AddResult(nsIXULTemplateResult* aResult,
+ nsIDOMNode* aQueryNode)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_ARG_POINTER(aQueryNode);
+
+ return UpdateResult(nullptr, aResult, aQueryNode);
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::RemoveResult(nsIXULTemplateResult* aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ return UpdateResult(aResult, nullptr, nullptr);
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::ReplaceResult(nsIXULTemplateResult* aOldResult,
+ nsIXULTemplateResult* aNewResult,
+ nsIDOMNode* aQueryNode)
+{
+ NS_ENSURE_ARG_POINTER(aOldResult);
+ NS_ENSURE_ARG_POINTER(aNewResult);
+ NS_ENSURE_ARG_POINTER(aQueryNode);
+
+ // just remove the old result and then add a new result separately
+
+ nsresult rv = UpdateResult(aOldResult, nullptr, nullptr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ return UpdateResult(nullptr, aNewResult, aQueryNode);
+}
+
+nsresult
+nsXULTemplateBuilder::UpdateResult(nsIXULTemplateResult* aOldResult,
+ nsIXULTemplateResult* aNewResult,
+ nsIDOMNode* aQueryNode)
+{
+ MOZ_LOG(gXULTemplateLog, LogLevel::Info,
+ ("nsXULTemplateBuilder::UpdateResult %p %p %p",
+ aOldResult, aNewResult, aQueryNode));
+
+ if (!mRoot || !mQueriesCompiled)
+ return NS_OK;
+
+ // get the containers where content may be inserted. If
+ // GetInsertionLocations returns false, no container has generated
+ // any content yet so new content should not be generated either. This
+ // will be false if the result applies to content that is in a closed menu
+ // or treeitem for example.
+
+ nsAutoPtr<nsCOMArray<nsIContent> > insertionPoints;
+ bool mayReplace = GetInsertionLocations(aOldResult ? aOldResult : aNewResult,
+ getter_Transfers(insertionPoints));
+ if (! mayReplace)
+ return NS_OK;
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIRDFResource> oldId, newId;
+ nsTemplateQuerySet* queryset = nullptr;
+
+ if (aOldResult) {
+ rv = GetResultResource(aOldResult, getter_AddRefs(oldId));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Ignore re-entrant builds for content that is currently in our
+ // activation stack.
+ if (IsActivated(oldId))
+ return NS_OK;
+ }
+
+ if (aNewResult) {
+ rv = GetResultResource(aNewResult, getter_AddRefs(newId));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // skip results that don't have ids
+ if (! newId)
+ return NS_OK;
+
+ // Ignore re-entrant builds for content that is currently in our
+ // activation stack.
+ if (IsActivated(newId))
+ return NS_OK;
+
+ // look for the queryset associated with the supplied query node
+ nsCOMPtr<nsIContent> querycontent = do_QueryInterface(aQueryNode);
+
+ int32_t count = mQuerySets.Length();
+ for (int32_t q = 0; q < count; q++) {
+ nsTemplateQuerySet* qs = mQuerySets[q];
+ if (qs->mQueryNode == querycontent) {
+ queryset = qs;
+ break;
+ }
+ }
+
+ if (! queryset)
+ return NS_OK;
+ }
+
+ if (insertionPoints) {
+ // iterate over each insertion point and add or remove the result from
+ // that container
+ uint32_t count = insertionPoints->Count();
+ for (uint32_t t = 0; t < count; t++) {
+ nsCOMPtr<nsIContent> insertionPoint = insertionPoints->SafeObjectAt(t);
+ if (insertionPoint) {
+ rv = UpdateResultInContainer(aOldResult, aNewResult, queryset,
+ oldId, newId, insertionPoint);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ }
+ }
+ else {
+ // The tree builder doesn't use insertion points, so no insertion
+ // points will be set. In this case, just update the one result.
+ rv = UpdateResultInContainer(aOldResult, aNewResult, queryset,
+ oldId, newId, nullptr);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateBuilder::UpdateResultInContainer(nsIXULTemplateResult* aOldResult,
+ nsIXULTemplateResult* aNewResult,
+ nsTemplateQuerySet* aQuerySet,
+ nsIRDFResource* aOldId,
+ nsIRDFResource* aNewId,
+ nsIContent* aInsertionPoint)
+{
+ // This method takes a result that no longer applies (aOldResult) and
+ // replaces it with a new result (aNewResult). Either may be null
+ // indicating to just remove a result or add a new one without replacing.
+ //
+ // Matches are stored in the hashtable mMatchMap, keyed by result id. If
+ // there is more than one query, or the same id is found in different
+ // containers, the values in the hashtable will be a linked list of all
+ // the matches for that id. The matches are sorted according to the
+ // queries they are associated with. Matches for earlier queries in the
+ // template take priority over matches from later queries. The priority
+ // for a match is determined from the match's QuerySetPriority method.
+ // The first query has a priority 0, and higher numbers are for later
+ // queries with successively higher priorities. Thus, a match takes
+ // precedence if it has a lower priority than another. If there is only
+ // one query or container, then the match doesn't have any linked items.
+ //
+ // Matches are nsTemplateMatch objects. They are wrappers around
+ // nsIXULTemplateResult result objects and are created with
+ // nsTemplateMatch::Create below. The aQuerySet argument specifies which
+ // query the match is associated with.
+ //
+ // When a result id exists in multiple containers, the match's mContainer
+ // field is set to the container it corresponds to. The aInsertionPoint
+ // argument specifies which container is being updated. Even though they
+ // are stored in the same linked list as other matches of the same id, the
+ // matches for different containers are treated separately. They are only
+ // stored in the same hashtable to avoid a more complex data structure, as
+ // the use of the same id in multiple containers isn't a common occurance.
+ //
+ // Only one match with a given id per container is active at a time. When
+ // a match is active, content is generated for it. When a match is
+ // inactive, content is not generated for it. A match becomes active if
+ // another match with the same id and container with a lower priority
+ // isn't already active, and the match has a rule or conditions clause
+ // which evaluates to true. The former is checked by comparing the value
+ // of the QuerySetPriority method of the match with earlier matches. The
+ // latter is checked with the DetermineMatchedRule method.
+ //
+ // Naturally, if a match with a lower priority is active, it overrides
+ // the new match, so the new match is hooked up into the match linked
+ // list as inactive, and no content is generated for it. If a match with a
+ // higher priority is active, and the new match's conditions evaluate
+ // to true, then this existing match with the higher priority needs to have
+ // its generated content removed and replaced with the new match's
+ // generated content.
+ //
+ // Similar situations apply when removing an existing match. If the match
+ // is active, the existing generated content will need to be removed, and
+ // a match of higher priority that is revealed may become active and need
+ // to have content generated.
+ //
+ // Content removal and generation is done by the ReplaceMatch method which
+ // is overridden for the content builder and tree builder to update the
+ // generated output for each type.
+ //
+ // The code below handles all of the various cases and ensures that the
+ // match lists are maintained properly.
+
+ nsresult rv = NS_OK;
+ int16_t ruleindex;
+ nsTemplateRule* matchedrule = nullptr;
+
+ // Indicates that the old match was active and must have its content
+ // removed
+ bool oldMatchWasActive = false;
+
+ // acceptedmatch will be set to a new match that has to have new content
+ // generated for it. If a new match doesn't need to have content
+ // generated, (because for example, a match with a lower priority
+ // already applies), then acceptedmatch will be null, but the match will
+ // be still hooked up into the chain, since it may become active later
+ // as other results are updated.
+ nsTemplateMatch* acceptedmatch = nullptr;
+
+ // When aOldResult is specified, removematch will be set to the
+ // corresponding match. This match needs to be deleted as it no longer
+ // applies. However, removedmatch will be null when aOldResult is null, or
+ // when no match was found corresponding to aOldResult.
+ nsTemplateMatch* removedmatch = nullptr;
+
+ // These will be set when aNewResult is specified indicating to add a
+ // result, but will end up replacing an existing match. The former
+ // indicates a match being replaced that was active and had content
+ // generated for it, while the latter indicates a match that wasn't active
+ // and just needs to be deleted. Both may point to different matches. For
+ // example, if the new match becomes active, replacing an inactive match,
+ // the inactive match will need to be deleted. However, if another match
+ // with a higher priority is active, the new match will override it, so
+ // content will need to be generated for the new match and removed for
+ // this existing active match.
+ nsTemplateMatch* replacedmatch = nullptr;
+ nsTemplateMatch* replacedmatchtodelete = nullptr;
+
+ if (aOldResult) {
+ nsTemplateMatch* firstmatch;
+ if (mMatchMap.Get(aOldId, &firstmatch)) {
+ nsTemplateMatch* oldmatch = firstmatch;
+ nsTemplateMatch* prevmatch = nullptr;
+
+ // look for the right match if there was more than one
+ while (oldmatch && (oldmatch->mResult != aOldResult)) {
+ prevmatch = oldmatch;
+ oldmatch = oldmatch->mNext;
+ }
+
+ if (oldmatch) {
+ nsTemplateMatch* findmatch = oldmatch->mNext;
+
+ // Keep a reference so that linked list can be hooked up at
+ // the end in case an error occurs.
+ nsTemplateMatch* nextmatch = findmatch;
+
+ if (oldmatch->IsActive()) {
+ // Indicate that the old match was active so its content
+ // will be removed later.
+ oldMatchWasActive = true;
+
+ // The match being removed is the active match, so scan
+ // through the later matches to determine if one should
+ // now become the active match.
+ while (findmatch) {
+ // only other matches with the same container should
+ // now match, leave other containers alone
+ if (findmatch->GetContainer() == aInsertionPoint) {
+ nsTemplateQuerySet* qs =
+ mQuerySets[findmatch->QuerySetPriority()];
+
+ DetermineMatchedRule(aInsertionPoint, findmatch->mResult,
+ qs, &matchedrule, &ruleindex);
+
+ if (matchedrule) {
+ rv = findmatch->RuleMatched(qs,
+ matchedrule, ruleindex,
+ findmatch->mResult);
+ if (NS_FAILED(rv))
+ return rv;
+
+ acceptedmatch = findmatch;
+ break;
+ }
+ }
+
+ findmatch = findmatch->mNext;
+ }
+ }
+
+ if (oldmatch == firstmatch) {
+ // the match to remove is at the beginning
+ if (oldmatch->mNext) {
+ mMatchMap.Put(aOldId, oldmatch->mNext);
+ }
+ else {
+ mMatchMap.Remove(aOldId);
+ }
+ }
+
+ if (prevmatch)
+ prevmatch->mNext = nextmatch;
+
+ removedmatch = oldmatch;
+ if (mFlags & eLoggingEnabled)
+ OutputMatchToLog(aOldId, removedmatch, false);
+ }
+ }
+ }
+
+ nsTemplateMatch *newmatch = nullptr;
+ if (aNewResult) {
+ // only allow a result to be inserted into containers with a matching tag
+ nsIAtom* tag = aQuerySet->GetTag();
+ if (aInsertionPoint && tag &&
+ tag != aInsertionPoint->NodeInfo()->NameAtom())
+ return NS_OK;
+
+ int32_t findpriority = aQuerySet->Priority();
+
+ newmatch = nsTemplateMatch::Create(findpriority,
+ aNewResult, aInsertionPoint);
+ if (!newmatch)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsTemplateMatch* firstmatch;
+ if (mMatchMap.Get(aNewId, &firstmatch)) {
+ bool hasEarlierActiveMatch = false;
+
+ // Scan through the existing matches to find where the new one
+ // should be inserted. oldmatch will be set to the old match for
+ // the same query and prevmatch will be set to the match before it.
+ nsTemplateMatch* prevmatch = nullptr;
+ nsTemplateMatch* oldmatch = firstmatch;
+ while (oldmatch) {
+ // Break out once we've reached a query in the list with a
+ // lower priority. The new match will be inserted at this
+ // location so that the match list is sorted by priority.
+ int32_t priority = oldmatch->QuerySetPriority();
+ if (priority > findpriority) {
+ oldmatch = nullptr;
+ break;
+ }
+
+ // look for matches that belong in the same container
+ if (oldmatch->GetContainer() == aInsertionPoint) {
+ if (priority == findpriority)
+ break;
+
+ // If a match with a lower priority is active, the new
+ // match can't replace it.
+ if (oldmatch->IsActive())
+ hasEarlierActiveMatch = true;
+ }
+
+ prevmatch = oldmatch;
+ oldmatch = oldmatch->mNext;
+ }
+
+ // At this point, oldmatch will either be null, or set to a match
+ // with the same container and priority. If set, oldmatch will
+ // need to be replaced by newmatch.
+
+ if (oldmatch)
+ newmatch->mNext = oldmatch->mNext;
+ else if (prevmatch)
+ newmatch->mNext = prevmatch->mNext;
+ else
+ newmatch->mNext = firstmatch;
+
+ // hasEarlierActiveMatch will be set to true if a match with a
+ // lower priority was found. The new match won't replace it in
+ // this case. If hasEarlierActiveMatch is false, then the new match
+ // may be become active if it matches one of the rules, and will
+ // generate output. It's also possible however, that a match with
+ // the same priority already exists, which means that the new match
+ // will replace the old one. In this case, oldmatch will be set to
+ // the old match. The content for the old match must be removed and
+ // content for the new match generated in its place.
+ if (! hasEarlierActiveMatch) {
+ // If the old match was the active match, set replacedmatch to
+ // indicate that it needs its content removed.
+ if (oldmatch) {
+ if (oldmatch->IsActive())
+ replacedmatch = oldmatch;
+ replacedmatchtodelete = oldmatch;
+ }
+
+ // check if the new result matches the rules
+ rv = DetermineMatchedRule(aInsertionPoint, newmatch->mResult,
+ aQuerySet, &matchedrule, &ruleindex);
+ if (NS_FAILED(rv)) {
+ nsTemplateMatch::Destroy(newmatch, false);
+ return rv;
+ }
+
+ if (matchedrule) {
+ rv = newmatch->RuleMatched(aQuerySet,
+ matchedrule, ruleindex,
+ newmatch->mResult);
+ if (NS_FAILED(rv)) {
+ nsTemplateMatch::Destroy(newmatch, false);
+ return rv;
+ }
+
+ // acceptedmatch may have been set in the block handling
+ // aOldResult earlier. If so, we would only get here when
+ // that match has a higher priority than this new match.
+ // As only one match can have content generated for it, it
+ // is OK to set acceptedmatch here to the new match,
+ // ignoring the other one.
+ acceptedmatch = newmatch;
+
+ // Clear the matched state of the later results for the
+ // same container.
+ nsTemplateMatch* clearmatch = newmatch->mNext;
+ while (clearmatch) {
+ if (clearmatch->GetContainer() == aInsertionPoint &&
+ clearmatch->IsActive()) {
+ clearmatch->SetInactive();
+ // Replacedmatch should be null here. If not, it
+ // means that two matches were active which isn't
+ // a valid state
+ NS_ASSERTION(!replacedmatch,
+ "replaced match already set");
+ replacedmatch = clearmatch;
+ break;
+ }
+ clearmatch = clearmatch->mNext;
+ }
+ }
+ else if (oldmatch && oldmatch->IsActive()) {
+ // The result didn't match the rules, so look for a later
+ // one. However, only do this if the old match was the
+ // active match.
+ newmatch = newmatch->mNext;
+ while (newmatch) {
+ if (newmatch->GetContainer() == aInsertionPoint) {
+ rv = DetermineMatchedRule(aInsertionPoint, newmatch->mResult,
+ aQuerySet, &matchedrule, &ruleindex);
+ if (NS_FAILED(rv)) {
+ nsTemplateMatch::Destroy(newmatch, false);
+ return rv;
+ }
+
+ if (matchedrule) {
+ rv = newmatch->RuleMatched(aQuerySet,
+ matchedrule, ruleindex,
+ newmatch->mResult);
+ if (NS_FAILED(rv)) {
+ nsTemplateMatch::Destroy(newmatch, false);
+ return rv;
+ }
+
+ acceptedmatch = newmatch;
+ break;
+ }
+ }
+
+ newmatch = newmatch->mNext;
+ }
+ }
+
+ // put the match in the map if there isn't a previous match
+ if (! prevmatch) {
+ mMatchMap.Put(aNewId, newmatch);
+ }
+ }
+
+ // hook up the match last in case an error occurs
+ if (prevmatch)
+ prevmatch->mNext = newmatch;
+ }
+ else {
+ // The id is not used in the hashtable yet so create a new match
+ // and add it to the hashtable.
+ rv = DetermineMatchedRule(aInsertionPoint, aNewResult,
+ aQuerySet, &matchedrule, &ruleindex);
+ if (NS_FAILED(rv)) {
+ nsTemplateMatch::Destroy(newmatch, false);
+ return rv;
+ }
+
+ if (matchedrule) {
+ rv = newmatch->RuleMatched(aQuerySet, matchedrule,
+ ruleindex, aNewResult);
+ if (NS_FAILED(rv)) {
+ nsTemplateMatch::Destroy(newmatch, false);
+ return rv;
+ }
+
+ acceptedmatch = newmatch;
+ }
+
+ mMatchMap.Put(aNewId, newmatch);
+ }
+ }
+
+ // 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) {
+ rv = ReplaceMatch(replacedmatch->mResult, nullptr, nullptr,
+ aInsertionPoint);
+
+ if (mFlags & eLoggingEnabled)
+ OutputMatchToLog(aNewId, replacedmatch, false);
+ }
+
+ // remove a match that needs to be deleted.
+ if (replacedmatchtodelete)
+ nsTemplateMatch::Destroy(replacedmatchtodelete, 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
+ // content needs to be generated for a new match.
+ if (oldMatchWasActive || acceptedmatch)
+ rv = ReplaceMatch(oldMatchWasActive ? aOldResult : nullptr,
+ acceptedmatch, matchedrule, aInsertionPoint);
+
+ // delete the old match that was replaced
+ if (removedmatch)
+ nsTemplateMatch::Destroy(removedmatch, true);
+
+ if (mFlags & eLoggingEnabled && newmatch)
+ OutputMatchToLog(aNewId, newmatch, 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.
+ // The new result will have the new values.
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!mRoot || !mQueriesCompiled)
+ return NS_OK;
+
+ return SynchronizeResult(aResult);
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::GetRootResult(nsIXULTemplateResult** aResult)
+{
+ *aResult = mRootResult;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::GetResultForId(const nsAString& aId,
+ nsIXULTemplateResult** aResult)
+{
+ if (aId.IsEmpty())
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIRDFResource> resource;
+ gRDFService->GetUnicodeResource(aId, getter_AddRefs(resource));
+
+ *aResult = nullptr;
+
+ nsTemplateMatch* match;
+ if (mMatchMap.Get(resource, &match)) {
+ // find the active match
+ while (match) {
+ if (match->IsActive()) {
+ *aResult = match->mResult;
+ NS_IF_ADDREF(*aResult);
+ break;
+ }
+ match = match->mNext;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::GetResultForContent(nsIDOMElement* aContent,
+ nsIXULTemplateResult** aResult)
+{
+ *aResult = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::AddListener(nsIXULBuilderListener* aListener)
+{
+ NS_ENSURE_ARG(aListener);
+
+ if (!mListeners.AppendObject(aListener))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::RemoveListener(nsIXULBuilderListener* aListener)
+{
+ NS_ENSURE_ARG(aListener);
+
+ mListeners.RemoveObject(aListener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateBuilder::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ // Uuuuber hack to clean up circular references that the cycle collector
+ // doesn't know about. See bug 394514.
+ if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC)) {
+ if (nsCOMPtr<mozIDOMWindow> window = do_QueryInterface(aSubject)) {
+ nsCOMPtr<nsIDocument> doc =
+ nsPIDOMWindowInner::From(window)->GetExtantDoc();
+ if (doc && doc == mObservedDocument)
+ NodeWillBeDestroyed(doc);
+ }
+ }
+ return NS_OK;
+}
+//----------------------------------------------------------------------
+//
+// nsIDocumentOberver interface
+//
+
+void
+nsXULTemplateBuilder::AttributeChanged(nsIDocument* aDocument,
+ Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ if (aElement == mRoot && aNameSpaceID == kNameSpaceID_None) {
+ // Check for a change to the 'ref' attribute on an atom, in which
+ // case we may need to nuke and rebuild the entire content model
+ // beneath the element.
+ if (aAttribute == nsGkAtoms::ref)
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod(this, &nsXULTemplateBuilder::RunnableRebuild));
+
+ // Check for a change to the 'datasources' attribute. If so, setup
+ // mDB by parsing the new value and rebuild.
+ else if (aAttribute == nsGkAtoms::datasources) {
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod(this, &nsXULTemplateBuilder::RunnableLoadAndRebuild));
+ }
+ }
+}
+
+void
+nsXULTemplateBuilder::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ if (mRoot && nsContentUtils::ContentIsDescendantOf(mRoot, aChild)) {
+ RefPtr<nsXULTemplateBuilder> kungFuDeathGrip(this);
+
+ if (mQueryProcessor)
+ mQueryProcessor->Done();
+
+ // Pass false to Uninit since content is going away anyway
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod(this, &nsXULTemplateBuilder::UninitFalse));
+
+ MOZ_ASSERT(aDocument == mObservedDocument);
+ StopObserving();
+
+ nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(aDocument);
+ if (xuldoc)
+ xuldoc->SetTemplateBuilderFor(mRoot, nullptr);
+
+ // clear the template state when removing content so that template
+ // content will be regenerated again if the content is reinserted
+ nsXULElement *xulcontent = nsXULElement::FromContent(mRoot);
+ if (xulcontent)
+ xulcontent->ClearTemplateGenerated();
+
+ CleanUp(true);
+
+ mDB = nullptr;
+ mCompDB = nullptr;
+ mDataSource = nullptr;
+ }
+}
+
+void
+nsXULTemplateBuilder::NodeWillBeDestroyed(const nsINode* aNode)
+{
+ // The call to RemoveObserver could release the last reference to
+ // |this|, so hold another reference.
+ RefPtr<nsXULTemplateBuilder> kungFuDeathGrip(this);
+
+ // Break circular references
+ if (mQueryProcessor)
+ mQueryProcessor->Done();
+
+ mDataSource = nullptr;
+ mDB = nullptr;
+ mCompDB = nullptr;
+
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod(this, &nsXULTemplateBuilder::UninitTrue));
+}
+
+
+
+
+//----------------------------------------------------------------------
+//
+// Implementation methods
+//
+
+nsresult
+nsXULTemplateBuilder::LoadDataSources(nsIDocument* aDocument,
+ bool* aShouldDelayBuilding)
+{
+ NS_PRECONDITION(mRoot != nullptr, "not initialized");
+
+ nsresult rv;
+ bool isRDFQuery = false;
+
+ // we'll set these again later, after we create a new composite ds
+ mDB = nullptr;
+ mCompDB = nullptr;
+ mDataSource = nullptr;
+
+ *aShouldDelayBuilding = false;
+
+ nsAutoString datasources;
+ mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::datasources, datasources);
+
+ nsAutoString querytype;
+ mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::querytype, querytype);
+
+ // create the query processor. The querytype attribute on the root element
+ // may be used to create one of a specific type.
+
+ // XXX should non-chrome be restricted to specific names?
+ if (querytype.IsEmpty())
+ querytype.AssignLiteral("rdf");
+
+ if (querytype.EqualsLiteral("rdf")) {
+ isRDFQuery = true;
+ mQueryProcessor = new nsXULTemplateQueryProcessorRDF();
+ }
+ else if (querytype.EqualsLiteral("xml")) {
+ mQueryProcessor = new nsXULTemplateQueryProcessorXML();
+ }
+ else if (querytype.EqualsLiteral("storage")) {
+ mQueryProcessor = new nsXULTemplateQueryProcessorStorage();
+ }
+ else {
+ nsAutoCString cid(NS_QUERY_PROCESSOR_CONTRACTID_PREFIX);
+ AppendUTF16toUTF8(querytype, cid);
+ mQueryProcessor = do_CreateInstance(cid.get(), &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.
+ nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(aDocument);
+ if (xuldoc)
+ xuldoc->SetTemplateBuilderFor(mRoot, this);
+
+ if (!mRoot->IsXULElement()) {
+ // Hmm. This must be an HTML element. Try to set it as a
+ // JS property "by hand".
+ InitHTMLTemplateRoot();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateBuilder::LoadDataSourceUrls(nsIDocument* aDocument,
+ const nsAString& aDataSources,
+ bool aIsRDFQuery,
+ bool* aShouldDelayBuilding)
+{
+ // Grab the doc's principal...
+ nsIPrincipal *docPrincipal = aDocument->NodePrincipal();
+
+ NS_ASSERTION(docPrincipal == mRoot->NodePrincipal(),
+ "Principal mismatch? Which one to use?");
+
+ bool isTrusted = false;
+ nsresult rv = IsSystemPrincipal(docPrincipal, &isTrusted);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Parse datasources: they are assumed to be a whitespace
+ // separated list of URIs; e.g.,
+ //
+ // rdf:bookmarks rdf:history http://foo.bar.com/blah.cgi?baz=9
+ //
+ nsIURI *docurl = aDocument->GetDocumentURI();
+
+ nsCOMPtr<nsIMutableArray> uriList = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ if (!uriList)
+ return NS_ERROR_FAILURE;
+
+ nsAutoString datasources(aDataSources);
+ uint32_t first = 0;
+ while (1) {
+ while (first < datasources.Length() && nsCRT::IsAsciiSpace(datasources.CharAt(first)))
+ ++first;
+
+ if (first >= datasources.Length())
+ break;
+
+ uint32_t last = first;
+ while (last < datasources.Length() && !nsCRT::IsAsciiSpace(datasources.CharAt(last)))
+ ++last;
+
+ nsAutoString uriStr;
+ datasources.Mid(uriStr, first, last - first);
+ first = last + 1;
+
+ // A special 'dummy' datasource
+ if (uriStr.EqualsLiteral("rdf:null"))
+ continue;
+
+ if (uriStr.CharAt(0) == '#') {
+ // ok, the datasource is certainly a node of the current document
+ nsCOMPtr<nsIDOMDocument> domdoc = do_QueryInterface(aDocument);
+ nsCOMPtr<nsIDOMElement> dsnode;
+
+ domdoc->GetElementById(Substring(uriStr, 1),
+ getter_AddRefs(dsnode));
+
+ if (dsnode)
+ uriList->AppendElement(dsnode, false);
+ continue;
+ }
+
+ // N.B. that `failure' (e.g., because it's an unknown
+ // protocol) leaves uriStr unaltered.
+ NS_MakeAbsoluteURI(uriStr, uriStr, docurl);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), uriStr);
+ if (NS_FAILED(rv) || !uri)
+ continue; // Necko will barf if our URI is weird
+
+ // don't add the uri to the list if the document is not allowed to
+ // load it
+ if (!isTrusted && NS_FAILED(docPrincipal->CheckMayLoad(uri, true, false)))
+ continue;
+
+ uriList->AppendElement(uri, false);
+ }
+
+ nsCOMPtr<nsIDOMNode> rootNode = do_QueryInterface(mRoot);
+ rv = mQueryProcessor->GetDatasource(uriList,
+ rootNode,
+ isTrusted,
+ this,
+ aShouldDelayBuilding,
+ getter_AddRefs(mDataSource));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aIsRDFQuery && mDataSource) {
+ // check if we were given an inference engine type
+ nsCOMPtr<nsIRDFInferDataSource> inferDB = do_QueryInterface(mDataSource);
+ if (inferDB) {
+ nsCOMPtr<nsIRDFDataSource> ds;
+ inferDB->GetBaseDataSource(getter_AddRefs(ds));
+ if (ds)
+ mCompDB = do_QueryInterface(ds);
+ }
+
+ if (!mCompDB)
+ mCompDB = do_QueryInterface(mDataSource);
+
+ mDB = do_QueryInterface(mDataSource);
+ }
+
+ if (!mDB && isTrusted) {
+ gRDFService->GetDataSource("rdf:local-store", getter_AddRefs(mDB));
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateBuilder::InitHTMLTemplateRoot()
+{
+ // Use XPConnect and the JS APIs to whack mDB and this as the
+ // 'database' and 'builder' properties onto aElement.
+ nsresult rv;
+
+ nsCOMPtr<nsIDocument> doc = mRoot->GetComposedDoc();
+ NS_ASSERTION(doc, "no document");
+ if (! doc)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIScriptGlobalObject> global =
+ do_QueryInterface(doc->GetWindow());
+ if (! global)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIGlobalObject> innerWin =
+ do_QueryInterface(doc->GetInnerWindow());
+
+ // We are going to run script via JS_SetProperty, so we need a script entry
+ // point, but as this is XUL related it does not appear in the HTML spec.
+ AutoEntryScript aes(innerWin, "nsXULTemplateBuilder creation", true);
+ JSContext* jscontext = aes.cx();
+
+ JS::Rooted<JS::Value> v(jscontext);
+ rv = nsContentUtils::WrapNative(jscontext, mRoot, mRoot, &v);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ JS::Rooted<JSObject*> jselement(jscontext, v.toObjectOrNull());
+
+ if (mDB) {
+ // database
+ JS::Rooted<JS::Value> jsdatabase(jscontext);
+ rv = nsContentUtils::WrapNative(jscontext, mDB,
+ &NS_GET_IID(nsIRDFCompositeDataSource),
+ &jsdatabase);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool ok = JS_SetProperty(jscontext, jselement, "database", jsdatabase);
+ NS_ASSERTION(ok, "unable to set database property");
+ if (! ok)
+ return NS_ERROR_FAILURE;
+ }
+
+ {
+ // builder
+ JS::Rooted<JS::Value> jsbuilder(jscontext);
+ rv = nsContentUtils::WrapNative(jscontext,
+ static_cast<nsIXULTemplateBuilder*>(this),
+ &NS_GET_IID(nsIXULTemplateBuilder),
+ &jsbuilder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool ok = JS_SetProperty(jscontext, jselement, "builder", jsbuilder);
+ if (! ok)
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateBuilder::DetermineMatchedRule(nsIContent *aContainer,
+ nsIXULTemplateResult* aResult,
+ nsTemplateQuerySet* aQuerySet,
+ nsTemplateRule** aMatchedRule,
+ int16_t *aRuleIndex)
+{
+ // iterate through the rules and look for one that the result matches
+ int16_t count = aQuerySet->RuleCount();
+ for (int16_t r = 0; r < count; r++) {
+ nsTemplateRule* rule = aQuerySet->GetRuleAt(r);
+ // If a tag was specified, it must match the tag of the container
+ // where content is being inserted.
+ nsIAtom* tag = rule->GetTag();
+ if ((!aContainer || !tag ||
+ tag == aContainer->NodeInfo()->NameAtom()) &&
+ rule->CheckMatch(aResult)) {
+ *aMatchedRule = rule;
+ *aRuleIndex = r;
+ return NS_OK;
+ }
+ }
+
+ *aRuleIndex = -1;
+ *aMatchedRule = nullptr;
+ return NS_OK;
+}
+
+void
+nsXULTemplateBuilder::ParseAttribute(const nsAString& aAttributeValue,
+ void (*aVariableCallback)(nsXULTemplateBuilder*, const nsAString&, void*),
+ void (*aTextCallback)(nsXULTemplateBuilder*, const nsAString&, void*),
+ void* aClosure)
+{
+ nsAString::const_iterator done_parsing;
+ aAttributeValue.EndReading(done_parsing);
+
+ nsAString::const_iterator iter;
+ aAttributeValue.BeginReading(iter);
+
+ nsAString::const_iterator mark(iter), backup(iter);
+
+ for (; iter != done_parsing; backup = ++iter) {
+ // A variable is either prefixed with '?' (in the extended
+ // syntax) or "rdf:" (in the simple syntax).
+ bool isvar;
+ if (*iter == char16_t('?') && (++iter != done_parsing)) {
+ isvar = true;
+ }
+ else if ((*iter == char16_t('r') && (++iter != done_parsing)) &&
+ (*iter == char16_t('d') && (++iter != done_parsing)) &&
+ (*iter == char16_t('f') && (++iter != done_parsing)) &&
+ (*iter == char16_t(':') && (++iter != done_parsing))) {
+ isvar = true;
+ }
+ else {
+ isvar = false;
+ }
+
+ if (! isvar) {
+ // It's not a variable, or we ran off the end of the
+ // string after the initial variable prefix. Since we may
+ // have slurped down some characters before realizing that
+ // fact, back up to the point where we started.
+ iter = backup;
+ continue;
+ }
+ else if (backup != mark && aTextCallback) {
+ // Okay, we've found a variable, and there's some vanilla
+ // text that's been buffered up. Flush it.
+ (*aTextCallback)(this, Substring(mark, backup), aClosure);
+ }
+
+ if (*iter == char16_t('?')) {
+ // Well, it was not really a variable, but "??". We use one
+ // question mark (the second one, actually) literally.
+ mark = iter;
+ continue;
+ }
+
+ // Construct a substring that is the symbol we need to look up
+ // in the rule's symbol table. The symbol is terminated by a
+ // space character, a caret, or the end of the string,
+ // whichever comes first.
+ nsAString::const_iterator first(backup);
+
+ char16_t c = 0;
+ while (iter != done_parsing) {
+ c = *iter;
+ if ((c == char16_t(' ')) || (c == char16_t('^')))
+ break;
+
+ ++iter;
+ }
+
+ nsAString::const_iterator last(iter);
+
+ // Back up so we don't consume the terminating character
+ // *unless* the terminating character was a caret: the caret
+ // means "concatenate with no space in between".
+ if (c != char16_t('^'))
+ --iter;
+
+ (*aVariableCallback)(this, Substring(first, last), aClosure);
+ mark = iter;
+ ++mark;
+ }
+
+ if (backup != mark && aTextCallback) {
+ // If there's any text left over, then fire the text callback
+ (*aTextCallback)(this, Substring(mark, backup), aClosure);
+ }
+}
+
+
+struct MOZ_STACK_CLASS SubstituteTextClosure {
+ SubstituteTextClosure(nsIXULTemplateResult* aResult, nsAString& aString)
+ : result(aResult), str(aString) {}
+
+ // some datasources are lazily initialized or modified while values are
+ // being retrieved, causing results to be removed. Due to this, hold a
+ // strong reference to the result.
+ nsCOMPtr<nsIXULTemplateResult> result;
+ nsAString& str;
+};
+
+nsresult
+nsXULTemplateBuilder::SubstituteText(nsIXULTemplateResult* aResult,
+ const nsAString& aAttributeValue,
+ nsAString& aString)
+{
+ // See if it's the special value "..."
+ if (aAttributeValue.EqualsLiteral("...")) {
+ aResult->GetId(aString);
+ return NS_OK;
+ }
+
+ // Reasonable guess at how big it should be
+ aString.SetCapacity(aAttributeValue.Length());
+
+ SubstituteTextClosure closure(aResult, aString);
+ ParseAttribute(aAttributeValue,
+ SubstituteTextReplaceVariable,
+ SubstituteTextAppendText,
+ &closure);
+
+ return NS_OK;
+}
+
+
+void
+nsXULTemplateBuilder::SubstituteTextAppendText(nsXULTemplateBuilder* aThis,
+ const nsAString& aText,
+ void* aClosure)
+{
+ // Append aString to the closure's result
+ SubstituteTextClosure* c = static_cast<SubstituteTextClosure*>(aClosure);
+ c->str.Append(aText);
+}
+
+void
+nsXULTemplateBuilder::SubstituteTextReplaceVariable(nsXULTemplateBuilder* aThis,
+ const nsAString& aVariable,
+ void* aClosure)
+{
+ // Substitute the value for the variable and append to the
+ // closure's result.
+ SubstituteTextClosure* c = static_cast<SubstituteTextClosure*>(aClosure);
+
+ nsAutoString replacementText;
+
+ // The symbol "rdf:*" is special, and means "this guy's URI"
+ if (aVariable.EqualsLiteral("rdf:*")){
+ c->result->GetId(replacementText);
+ }
+ else {
+ // Got a variable; get the value it's assigned to
+ nsCOMPtr<nsIAtom> var = NS_Atomize(aVariable);
+ c->result->GetBindingFor(var, replacementText);
+ }
+
+ c->str += replacementText;
+}
+
+bool
+nsXULTemplateBuilder::IsTemplateElement(nsIContent* aContent)
+{
+ return aContent->NodeInfo()->Equals(nsGkAtoms::_template,
+ kNameSpaceID_XUL);
+}
+
+nsresult
+nsXULTemplateBuilder::GetTemplateRoot(nsIContent** aResult)
+{
+ NS_PRECONDITION(mRoot != nullptr, "not initialized");
+ if (! mRoot)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ // First, check and see if the root has a template attribute. This
+ // allows a template to be specified "out of line"; e.g.,
+ //
+ // <window>
+ // <foo template="MyTemplate">...</foo>
+ // <template id="MyTemplate">...</template>
+ // </window>
+ //
+ nsAutoString templateID;
+ mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::_template, templateID);
+
+ if (! templateID.IsEmpty()) {
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mRoot->GetComposedDoc());
+ if (! domDoc)
+ return NS_OK;
+
+ nsCOMPtr<nsIDOMElement> domElement;
+ domDoc->GetElementById(templateID, getter_AddRefs(domElement));
+
+ if (domElement) {
+ nsCOMPtr<nsIContent> content = do_QueryInterface(domElement);
+ NS_ENSURE_STATE(content &&
+ !nsContentUtils::ContentIsDescendantOf(mRoot,
+ content));
+ content.forget(aResult);
+ return NS_OK;
+ }
+ }
+
+ // If root node has no template attribute, then look for a child
+ // node which is a template tag.
+ for (nsIContent* child = mRoot->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+
+ if (IsTemplateElement(child)) {
+ NS_ADDREF(*aResult = child);
+ return NS_OK;
+ }
+ }
+
+ // Look through the anonymous children as well. Although FlattenedChildIterator
+ // will find a template element that has been placed in an insertion point, many
+ // bindings do not have a specific insertion point for the template element, which
+ // would cause it to not be part of the flattened content tree. The check above to
+ // check the explicit children as well handles this case.
+ FlattenedChildIterator iter(mRoot);
+ for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
+ if (IsTemplateElement(child)) {
+ NS_ADDREF(*aResult = child);
+ return NS_OK;
+ }
+ }
+
+ *aResult = nullptr;
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateBuilder::CompileQueries()
+{
+ nsCOMPtr<nsIContent> tmpl;
+ GetTemplateRoot(getter_AddRefs(tmpl));
+ if (! tmpl)
+ return NS_OK;
+
+ if (! mRoot)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ // 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. 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;
+ }
+
+ // always enable logging if the debug setting is used
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug))
+ mFlags |= eLoggingEnabled;
+
+ 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
+ // attribute on the <template> and the member variable may be specified
+ // using the member attribute or the value of the uri attribute inside the
+ // first action body in the template. If not specified, the container
+ // variable defaults to '?uri' and the member variable defaults to '?' or
+ // 'rdf:*' for simple queries.
+
+ // For RDF queries, the container variable may also be set via the
+ // <content> tag.
+
+ nsAutoString containervar;
+ tmpl->GetAttr(kNameSpaceID_None, nsGkAtoms::container, containervar);
+
+ if (containervar.IsEmpty())
+ mRefVariable = NS_Atomize("?uri");
+ else
+ mRefVariable = NS_Atomize(containervar);
+
+ nsAutoString membervar;
+ tmpl->GetAttr(kNameSpaceID_None, nsGkAtoms::member, membervar);
+
+ if (membervar.IsEmpty())
+ mMemberVariable = nullptr;
+ else
+ mMemberVariable = NS_Atomize(membervar);
+
+ nsTemplateQuerySet* queryset = new nsTemplateQuerySet(0);
+ if (!mQuerySets.AppendElement(queryset)) {
+ delete queryset;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ bool canUseTemplate = false;
+ int32_t priority = 0;
+ rv = CompileTemplate(tmpl, queryset, false, &priority, &canUseTemplate);
+
+ if (NS_FAILED(rv) || !canUseTemplate) {
+ for (int32_t q = mQuerySets.Length() - 1; q >= 0; q--) {
+ nsTemplateQuerySet* qs = mQuerySets[q];
+ delete qs;
+ }
+ mQuerySets.Clear();
+ }
+
+ mQueriesCompiled = true;
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateBuilder::CompileTemplate(nsIContent* aTemplate,
+ nsTemplateQuerySet* aQuerySet,
+ bool aIsQuerySet,
+ int32_t* aPriority,
+ bool* aCanUseTemplate)
+{
+ NS_ASSERTION(aQuerySet, "No queryset supplied");
+
+ nsresult rv = NS_OK;
+
+ bool isQuerySetMode = false;
+ bool hasQuerySet = false, hasRule = false, hasQuery = false;
+
+ for (nsIContent* rulenode = aTemplate->GetFirstChild();
+ rulenode;
+ rulenode = rulenode->GetNextSibling()) {
+
+ mozilla::dom::NodeInfo *ni = rulenode->NodeInfo();
+
+ // don't allow more queries than can be supported
+ if (*aPriority == 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) {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_INVALID_QUERYSET);
+ continue;
+ }
+
+ isQuerySetMode = 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);
+
+ // once the queryset is appended to the mQuerySets list, it
+ // will be removed by CompileQueries if an error occurs
+ if (!mQuerySets.AppendElement(aQuerySet)) {
+ delete aQuerySet;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ hasQuerySet = true;
+
+ rv = CompileTemplate(rulenode, aQuerySet, true, aPriority, aCanUseTemplate);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ // once a queryset is used, everything must be a queryset
+ if (isQuerySetMode)
+ continue;
+
+ if (ni->Equals(nsGkAtoms::rule, kNameSpaceID_XUL)) {
+ nsCOMPtr<nsIContent> action;
+ nsXULContentUtils::FindChildByTag(rulenode,
+ kNameSpaceID_XUL,
+ nsGkAtoms::action,
+ getter_AddRefs(action));
+
+ if (action){
+ nsCOMPtr<nsIAtom> memberVariable = mMemberVariable;
+ if (!memberVariable) {
+ memberVariable = DetermineMemberVariable(action);
+ 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);
+
+ if (! aQuerySet->mCompiledQuery) {
+ nsCOMPtr<nsIDOMNode> query(do_QueryInterface(aQuerySet->mQueryNode));
+
+ rv = mQueryProcessor->CompileQuery(this, query,
+ mRefVariable, memberVariable,
+ getter_AddRefs(aQuerySet->mCompiledQuery));
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ if (aQuerySet->mCompiledQuery) {
+ rv = CompileExtendedQuery(rulenode, action, memberVariable,
+ aQuerySet);
+ if (NS_FAILED(rv))
+ return rv;
+
+ *aCanUseTemplate = true;
+ }
+ }
+ else {
+ // backwards-compatible RDF template syntax where there is
+ // an <action> node but no <query> node. In this case,
+ // use the conditions as if it was the query.
+
+ nsCOMPtr<nsIContent> conditions;
+ nsXULContentUtils::FindChildByTag(rulenode,
+ kNameSpaceID_XUL,
+ nsGkAtoms::conditions,
+ getter_AddRefs(conditions));
+
+ if (conditions) {
+ // create a new queryset if one hasn't been created already
+ if (hasQuerySet) {
+ aQuerySet = new nsTemplateQuerySet(++*aPriority);
+ if (!mQuerySets.AppendElement(aQuerySet)) {
+ delete aQuerySet;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ nsCOMPtr<nsIAtom> tag;
+ DetermineRDFQueryRef(conditions, getter_AddRefs(tag));
+ if (tag)
+ aQuerySet->SetTag(tag);
+
+ hasQuerySet = true;
+
+ nsCOMPtr<nsIDOMNode> conditionsnode(do_QueryInterface(conditions));
+
+ aQuerySet->mQueryNode = conditions;
+ rv = mQueryProcessor->CompileQuery(this, conditionsnode,
+ mRefVariable,
+ memberVariable,
+ getter_AddRefs(aQuerySet->mCompiledQuery));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (aQuerySet->mCompiledQuery) {
+ rv = CompileExtendedQuery(rulenode, action, memberVariable,
+ aQuerySet);
+ if (NS_FAILED(rv))
+ return rv;
+
+ *aCanUseTemplate = true;
+ }
+ }
+ }
+ }
+ else {
+ if (hasQuery)
+ continue;
+
+ // a new queryset must always be created in this case
+ if (hasQuerySet) {
+ aQuerySet = new nsTemplateQuerySet(++*aPriority);
+ if (!mQuerySets.AppendElement(aQuerySet)) {
+ delete aQuerySet;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ hasQuerySet = true;
+
+ rv = CompileSimpleQuery(rulenode, aQuerySet, aCanUseTemplate);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ hasRule = true;
+ }
+ else if (ni->Equals(nsGkAtoms::query, kNameSpaceID_XUL)) {
+ if (hasQuery)
+ continue;
+
+ aQuerySet->mQueryNode = rulenode;
+ hasQuery = true;
+ }
+ else if (ni->Equals(nsGkAtoms::action, kNameSpaceID_XUL)) {
+ // the query must appear before the action
+ if (! hasQuery)
+ continue;
+
+ 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) {
+ 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));
+
+ if (aQuerySet->mCompiledQuery) {
+ nsTemplateRule* rule = aQuerySet->NewRule(aTemplate, rulenode, aQuerySet);
+ if (! rule)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rule->SetVars(mRefVariable, memberVariable);
+
+ *aCanUseTemplate = true;
+
+ return NS_OK;
+ }
+ }
+ }
+
+ if (! hasRule && ! hasQuery && ! hasQuerySet) {
+ // if no rules are specified in the template, then the contents of the
+ // <template> tag are the one-and-only template.
+ rv = CompileSimpleQuery(aTemplate, aQuerySet, aCanUseTemplate);
+ }
+
+ return rv;
+}
+
+nsresult
+nsXULTemplateBuilder::CompileExtendedQuery(nsIContent* aRuleElement,
+ nsIContent* aActionElement,
+ nsIAtom* aMemberVariable,
+ nsTemplateQuerySet* aQuerySet)
+{
+ // Compile an "extended" <template> rule. An extended rule may have
+ // a <conditions> child, an <action> child, and a <bindings> child.
+ nsresult rv;
+
+ nsTemplateRule* rule = aQuerySet->NewRule(aRuleElement, aActionElement, aQuerySet);
+ if (! rule)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIContent> conditions;
+ nsXULContentUtils::FindChildByTag(aRuleElement,
+ kNameSpaceID_XUL,
+ nsGkAtoms::conditions,
+ getter_AddRefs(conditions));
+
+ // allow the conditions to be placed directly inside the rule
+ if (!conditions)
+ conditions = aRuleElement;
+
+ rv = CompileConditions(rule, conditions);
+ // If the rule compilation failed, then we have to bail.
+ if (NS_FAILED(rv)) {
+ aQuerySet->RemoveRule(rule);
+ return rv;
+ }
+
+ rule->SetVars(mRefVariable, aMemberVariable);
+
+ // If we've got bindings, add 'em.
+ nsCOMPtr<nsIContent> bindings;
+ nsXULContentUtils::FindChildByTag(aRuleElement,
+ kNameSpaceID_XUL,
+ nsGkAtoms::bindings,
+ getter_AddRefs(bindings));
+
+ // allow bindings to be placed directly inside rule
+ if (!bindings)
+ bindings = aRuleElement;
+
+ rv = CompileBindings(rule, bindings);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIAtom>
+nsXULTemplateBuilder::DetermineMemberVariable(nsIContent* aElement)
+{
+ // recursively iterate over the children looking for an element
+ // with uri="?..."
+ for (nsIContent* child = aElement->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ nsAutoString uri;
+ child->GetAttr(kNameSpaceID_None, nsGkAtoms::uri, uri);
+ if (!uri.IsEmpty() && uri[0] == char16_t('?')) {
+ return NS_Atomize(uri);
+ }
+
+ nsCOMPtr<nsIAtom> result = DetermineMemberVariable(child);
+ if (result) {
+ return result.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+void
+nsXULTemplateBuilder::DetermineRDFQueryRef(nsIContent* aQueryElement, nsIAtom** aTag)
+{
+ // check for a tag
+ nsCOMPtr<nsIContent> content;
+ nsXULContentUtils::FindChildByTag(aQueryElement,
+ kNameSpaceID_XUL,
+ nsGkAtoms::content,
+ getter_AddRefs(content));
+
+ if (! content) {
+ // look for older treeitem syntax as well
+ nsXULContentUtils::FindChildByTag(aQueryElement,
+ kNameSpaceID_XUL,
+ nsGkAtoms::treeitem,
+ getter_AddRefs(content));
+ }
+
+ if (content) {
+ nsAutoString uri;
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::uri, uri);
+
+ if (!uri.IsEmpty())
+ mRefVariable = NS_Atomize(uri);
+
+ nsAutoString tag;
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::tag, tag);
+
+ if (!tag.IsEmpty())
+ *aTag = NS_Atomize(tag).take();
+ }
+}
+
+nsresult
+nsXULTemplateBuilder::CompileSimpleQuery(nsIContent* aRuleElement,
+ nsTemplateQuerySet* aQuerySet,
+ bool* aCanUseTemplate)
+{
+ // compile a simple query, which is a query with no <query> or
+ // <conditions>. This means that a default query is used.
+ nsCOMPtr<nsIDOMNode> query(do_QueryInterface(aRuleElement));
+
+ nsCOMPtr<nsIAtom> memberVariable;
+ if (mMemberVariable)
+ memberVariable = mMemberVariable;
+ else
+ memberVariable = NS_Atomize("rdf:*");
+
+ // since there is no <query> node for a simple query, the query node will
+ // be either the <rule> node if multiple rules are used, or the <template> node.
+ aQuerySet->mQueryNode = aRuleElement;
+ nsresult rv = mQueryProcessor->CompileQuery(this, query,
+ mRefVariable, memberVariable,
+ getter_AddRefs(aQuerySet->mCompiledQuery));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (! aQuerySet->mCompiledQuery) {
+ *aCanUseTemplate = false;
+ return NS_OK;
+ }
+
+ nsTemplateRule* rule = aQuerySet->NewRule(aRuleElement, aRuleElement, aQuerySet);
+ if (! rule)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rule->SetVars(mRefVariable, memberVariable);
+
+ nsAutoString tag;
+ aRuleElement->GetAttr(kNameSpaceID_None, nsGkAtoms::parent, tag);
+
+ if (!tag.IsEmpty()) {
+ nsCOMPtr<nsIAtom> tagatom = NS_Atomize(tag);
+ aQuerySet->SetTag(tagatom);
+ }
+
+ *aCanUseTemplate = true;
+
+ return AddSimpleRuleBindings(rule, aRuleElement);
+}
+
+nsresult
+nsXULTemplateBuilder::CompileConditions(nsTemplateRule* aRule,
+ nsIContent* aCondition)
+{
+ nsAutoString tag;
+ aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::parent, tag);
+
+ if (!tag.IsEmpty()) {
+ nsCOMPtr<nsIAtom> tagatom = NS_Atomize(tag);
+ aRule->SetTag(tagatom);
+ }
+
+ nsTemplateCondition* currentCondition = nullptr;
+
+ for (nsIContent* node = aCondition->GetFirstChild();
+ node;
+ node = node->GetNextSibling()) {
+
+ if (node->NodeInfo()->Equals(nsGkAtoms::where, kNameSpaceID_XUL)) {
+ nsresult rv = CompileWhereCondition(aRule, node, &currentCondition);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateBuilder::CompileWhereCondition(nsTemplateRule* aRule,
+ nsIContent* aCondition,
+ nsTemplateCondition** aCurrentCondition)
+{
+ // Compile a <where> condition, which must be of the form:
+ //
+ // <where subject="?var1|string" rel="relation" value="?var2|string" />
+ //
+ // The value of rel may be:
+ // equal - subject must be equal to object
+ // notequal - subject must not be equal to object
+ // less - subject must be less than object
+ // greater - subject must be greater than object
+ // 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()) {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_SUBJECT);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIAtom> svar;
+ if (subject[0] == char16_t('?'))
+ svar = NS_Atomize(subject);
+
+ nsAutoString relstring;
+ aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, relstring);
+ 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()) {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_VALUE);
+ return NS_OK;
+ }
+
+ // multiple
+ bool shouldMultiple =
+ aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::multiple,
+ nsGkAtoms::_true, eCaseMatters);
+
+ nsCOMPtr<nsIAtom> vvar;
+ if (!shouldMultiple && (value[0] == char16_t('?'))) {
+ vvar = NS_Atomize(value);
+ }
+
+ // ignorecase
+ bool shouldIgnoreCase =
+ aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ignorecase,
+ nsGkAtoms::_true, eCaseMatters);
+
+ // negate
+ bool shouldNegate =
+ aCondition->AttrValueIs(kNameSpaceID_None, nsGkAtoms::negate,
+ nsGkAtoms::_true, eCaseMatters);
+
+ nsTemplateCondition* condition;
+
+ if (svar && vvar) {
+ condition = new nsTemplateCondition(svar, relstring, vvar,
+ shouldIgnoreCase, shouldNegate);
+ }
+ else if (svar && !value.IsEmpty()) {
+ condition = new nsTemplateCondition(svar, relstring, value,
+ shouldIgnoreCase, shouldNegate, shouldMultiple);
+ }
+ else if (vvar) {
+ condition = new nsTemplateCondition(subject, relstring, vvar,
+ shouldIgnoreCase, shouldNegate);
+ }
+ else {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_WHERE_NO_VAR);
+ return NS_OK;
+ }
+
+ if (*aCurrentCondition) {
+ (*aCurrentCondition)->SetNext(condition);
+ }
+ else {
+ aRule->SetCondition(condition);
+ }
+
+ *aCurrentCondition = condition;
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateBuilder::CompileBindings(nsTemplateRule* aRule, nsIContent* aBindings)
+{
+ // Add an extended rule's bindings.
+ nsresult rv;
+
+ for (nsIContent* binding = aBindings->GetFirstChild();
+ binding;
+ binding = binding->GetNextSibling()) {
+
+ if (binding->NodeInfo()->Equals(nsGkAtoms::binding,
+ kNameSpaceID_XUL)) {
+ rv = CompileBinding(aRule, binding);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ }
+
+ aRule->AddBindingsToQueryProcessor(mQueryProcessor);
+
+ return NS_OK;
+}
+
+
+nsresult
+nsXULTemplateBuilder::CompileBinding(nsTemplateRule* aRule,
+ nsIContent* aBinding)
+{
+ // Compile a <binding> "condition", which must be of the form:
+ //
+ // <binding subject="?var1"
+ // predicate="resource"
+ // 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()) {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_SUBJECT);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIAtom> svar;
+ if (subject[0] == char16_t('?')) {
+ svar = NS_Atomize(subject);
+ }
+ else {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_SUBJECT);
+ return NS_OK;
+ }
+
+ // predicate
+ nsAutoString predicate;
+ aBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::predicate, predicate);
+ if (predicate.IsEmpty()) {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_PREDICATE);
+ return NS_OK;
+ }
+
+ // object
+ nsAutoString object;
+ aBinding->GetAttr(kNameSpaceID_None, nsGkAtoms::object, object);
+
+ if (object.IsEmpty()) {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_OBJECT);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIAtom> ovar;
+ if (object[0] == char16_t('?')) {
+ ovar = NS_Atomize(object);
+ }
+ else {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BINDING_BAD_OBJECT);
+ return NS_OK;
+ }
+
+ return aRule->AddBinding(svar, predicate, ovar);
+}
+
+nsresult
+nsXULTemplateBuilder::AddSimpleRuleBindings(nsTemplateRule* aRule,
+ nsIContent* aElement)
+{
+ // Crawl the content tree of a "simple" rule, adding a variable
+ // assignment for any attribute whose value is "rdf:".
+
+ AutoTArray<nsIContent*, 8> elements;
+
+ if (elements.AppendElement(aElement) == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ while (elements.Length()) {
+ // Pop the next element off the stack
+ uint32_t i = elements.Length() - 1;
+ nsIContent* element = elements[i];
+ elements.RemoveElementAt(i);
+
+ // Iterate through its attributes, looking for substitutions
+ // that we need to add as bindings.
+ uint32_t count = element->GetAttrCount();
+
+ for (i = 0; i < count; ++i) {
+ const nsAttrName* name = element->GetAttrNameAt(i);
+
+ if (!name->Equals(nsGkAtoms::id, kNameSpaceID_None) &&
+ !name->Equals(nsGkAtoms::uri, kNameSpaceID_None)) {
+ nsAutoString value;
+ element->GetAttr(name->NamespaceID(), name->LocalName(), value);
+
+ // Scan the attribute for variables, adding a binding for
+ // each one.
+ ParseAttribute(value, AddBindingsFor, nullptr, aRule);
+ }
+ }
+
+ // Push kids onto the stack, and search them next.
+ for (nsIContent* child = element->GetLastChild();
+ child;
+ child = child->GetPreviousSibling()) {
+
+ if (!elements.AppendElement(child))
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ aRule->AddBindingsToQueryProcessor(mQueryProcessor);
+
+ return NS_OK;
+}
+
+void
+nsXULTemplateBuilder::AddBindingsFor(nsXULTemplateBuilder* aThis,
+ const nsAString& aVariable,
+ void* aClosure)
+{
+ // We should *only* be recieving "rdf:"-style variables. Make
+ // sure...
+ if (!StringBeginsWith(aVariable, NS_LITERAL_STRING("rdf:")))
+ return;
+
+ nsTemplateRule* rule = static_cast<nsTemplateRule*>(aClosure);
+
+ nsCOMPtr<nsIAtom> var = NS_Atomize(aVariable);
+
+ // Strip it down to the raw RDF property by clobbering the "rdf:"
+ // prefix
+ nsAutoString property;
+ property.Assign(Substring(aVariable, uint32_t(4), aVariable.Length() - 4));
+
+ if (! rule->HasBinding(rule->GetMemberVariable(), property, var))
+ // In the simple syntax, the binding is always from the
+ // member variable, through the property, to the target.
+ rule->AddBinding(rule->GetMemberVariable(), property, var);
+}
+
+
+nsresult
+nsXULTemplateBuilder::IsSystemPrincipal(nsIPrincipal *principal, bool *result)
+{
+ if (!gSystemPrincipal)
+ return NS_ERROR_UNEXPECTED;
+
+ *result = (principal == gSystemPrincipal);
+ return NS_OK;
+}
+
+bool
+nsXULTemplateBuilder::IsActivated(nsIRDFResource *aResource)
+{
+ for (ActivationEntry *entry = mTop;
+ entry != nullptr;
+ entry = entry->mPrevious) {
+ if (entry->mResource == aResource)
+ return true;
+ }
+ return false;
+}
+
+nsresult
+nsXULTemplateBuilder::GetResultResource(nsIXULTemplateResult* aResult,
+ nsIRDFResource** aResource)
+{
+ // get the resource for a result by checking its resource property. If it
+ // is not set, check the id. This allows non-chrome implementations to
+ // avoid having to use RDF.
+ nsresult rv = aResult->GetResource(aResource);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (! *aResource) {
+ nsAutoString id;
+ rv = aResult->GetId(id);
+ if (NS_FAILED(rv))
+ return rv;
+
+ return gRDFService->GetUnicodeResource(id, aResource);
+ }
+
+ return rv;
+}
+
+
+void
+nsXULTemplateBuilder::OutputMatchToLog(nsIRDFResource* aId,
+ nsTemplateMatch* aMatch,
+ bool aIsNew)
+{
+ int32_t priority = aMatch->QuerySetPriority() + 1;
+ int32_t 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 = nullptr;
+ 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());
+}
diff --git a/dom/xul/templates/nsXULTemplateBuilder.h b/dom/xul/templates/nsXULTemplateBuilder.h
new file mode 100644
index 000000000..7da8ffc98
--- /dev/null
+++ b/dom/xul/templates/nsXULTemplateBuilder.h
@@ -0,0 +1,502 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXULTemplateBuilder_h__
+#define nsXULTemplateBuilder_h__
+
+#include "nsStubDocumentObserver.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIObserver.h"
+#include "nsIRDFCompositeDataSource.h"
+#include "nsIRDFContainer.h"
+#include "nsIRDFContainerUtils.h"
+#include "nsIRDFDataSource.h"
+#include "nsIRDFObserver.h"
+#include "nsIRDFService.h"
+#include "nsIXULTemplateBuilder.h"
+
+#include "nsCOMArray.h"
+#include "nsTArray.h"
+#include "nsDataHashtable.h"
+#include "nsTemplateRule.h"
+#include "nsTemplateMatch.h"
+#include "nsIXULTemplateQueryProcessor.h"
+#include "nsCycleCollectionParticipant.h"
+
+#include "mozilla/Logging.h"
+extern mozilla::LazyLogModule gXULTemplateLog;
+
+class nsIContent;
+class nsIObserverService;
+class nsIRDFCompositeDataSource;
+
+/**
+ * An object that translates an RDF graph into a presentation using a
+ * set of rules.
+ */
+class nsXULTemplateBuilder : public nsIXULTemplateBuilder,
+ public nsIObserver,
+ public nsStubDocumentObserver
+{
+ void CleanUp(bool aIsFinal);
+ void DestroyMatchMap();
+
+public:
+ nsXULTemplateBuilder();
+
+ nsresult InitGlobals();
+
+ /**
+ * Clear the template builder structures. The aIsFinal flag is set to true
+ * when the template is going away.
+ */
+ virtual void Uninit(bool aIsFinal);
+
+ // nsISupports interface
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsXULTemplateBuilder,
+ nsIXULTemplateBuilder)
+
+ // nsIXULTemplateBuilder interface
+ NS_DECL_NSIXULTEMPLATEBUILDER
+
+ // nsIObserver Interface
+ NS_DECL_NSIOBSERVER
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+ /**
+ * Remove an old result and/or add a new result. This method will retrieve
+ * the set of containers where the result could be inserted and either add
+ * the new result to those containers, or remove the result from those
+ * containers. UpdateResultInContainer is called for each container.
+ *
+ * @param aOldResult result to remove
+ * @param aNewResult result to add
+ * @param aQueryNode query node for new result
+ */
+ nsresult
+ UpdateResult(nsIXULTemplateResult* aOldResult,
+ nsIXULTemplateResult* aNewResult,
+ nsIDOMNode* aQueryNode);
+
+ /**
+ * Remove an old result and/or add a new result from a specific container.
+ *
+ * @param aOldResult result to remove
+ * @param aNewResult result to add
+ * @param aQueryNode queryset for the new result
+ * @param aOldId id of old result
+ * @param aNewId id of new result
+ * @param aInsertionPoint container to remove or add result inside
+ */
+ nsresult
+ UpdateResultInContainer(nsIXULTemplateResult* aOldResult,
+ nsIXULTemplateResult* aNewResult,
+ nsTemplateQuerySet* aQuerySet,
+ nsIRDFResource* aOldId,
+ nsIRDFResource* aNewId,
+ nsIContent* aInsertionPoint);
+
+ nsresult
+ ComputeContainmentProperties();
+
+ static bool
+ IsTemplateElement(nsIContent* aContent);
+
+ virtual nsresult
+ RebuildAll() = 0; // must be implemented by subclasses
+
+ void RunnableRebuild() { Rebuild(); }
+ void RunnableLoadAndRebuild() {
+ Uninit(false); // Reset results
+
+ nsCOMPtr<nsIDocument> doc = mRoot ? mRoot->GetComposedDoc() : nullptr;
+ if (doc) {
+ bool shouldDelay;
+ LoadDataSources(doc, &shouldDelay);
+ if (!shouldDelay) {
+ Rebuild();
+ }
+ }
+ }
+
+ // mRoot should not be cleared until after Uninit is finished so that
+ // generated content can be removed during uninitialization.
+ void UninitFalse() { Uninit(false); mRoot = nullptr; }
+ void UninitTrue() { Uninit(true); mRoot = nullptr; }
+
+ /**
+ * Find the <template> tag that applies for this builder
+ */
+ nsresult
+ GetTemplateRoot(nsIContent** aResult);
+
+ /**
+ * Compile the template's queries
+ */
+ nsresult
+ CompileQueries();
+
+ /**
+ * Compile the template given a <template> in aTemplate. This function
+ * is called recursively to handle queries inside a queryset. For the
+ * outer pass, aIsQuerySet will be false, while the inner pass this will
+ * be true.
+ *
+ * aCanUseTemplate will be set to true if the template's queries could be
+ * compiled, and false otherwise. If false, the entire template is
+ * invalid.
+ *
+ * @param aTemplate <template> to compile
+ * @param aQuerySet first queryset
+ * @param aIsQuerySet true if
+ * @param aPriority the queryset index, incremented when a new one is added
+ * @param aCanUseTemplate true if template is valid
+ */
+ nsresult
+ CompileTemplate(nsIContent* aTemplate,
+ nsTemplateQuerySet* aQuerySet,
+ bool aIsQuerySet,
+ int32_t* aPriority,
+ bool* aCanUseTemplate);
+
+ /**
+ * Compile a query using the extended syntax. For backwards compatible RDF
+ * syntax where there is no <query>, the <conditions> becomes the query.
+ *
+ * @param aRuleElement <rule> element
+ * @param aActionElement <action> element
+ * @param aMemberVariable member variable for the query
+ * @param aQuerySet the queryset
+ */
+ nsresult
+ CompileExtendedQuery(nsIContent* aRuleElement,
+ nsIContent* aActionElement,
+ nsIAtom* aMemberVariable,
+ nsTemplateQuerySet* aQuerySet);
+
+ /**
+ * Determine the ref variable and tag from inside a RDF query.
+ */
+ void DetermineRDFQueryRef(nsIContent* aQueryElement, nsIAtom** tag);
+
+ /**
+ * Determine the member variable from inside an action body. It will be
+ * the value of the uri attribute on a node.
+ */
+ already_AddRefed<nsIAtom> DetermineMemberVariable(nsIContent* aElement);
+
+ /**
+ * Compile a simple query. A simple query is one that doesn't have a
+ * <query> and should use a default query which would normally just return
+ * a list of children of the reference point.
+ *
+ * @param aRuleElement the <rule>
+ * @param aQuerySet the query set
+ * @param aCanUseTemplate true if the query is valid
+ */
+ nsresult
+ CompileSimpleQuery(nsIContent* aRuleElement,
+ nsTemplateQuerySet* aQuerySet,
+ bool* aCanUseTemplate);
+
+ /**
+ * Compile the <conditions> tag in a rule
+ *
+ * @param aRule template rule
+ * @param aConditions <conditions> element
+ */
+ nsresult
+ CompileConditions(nsTemplateRule* aRule, nsIContent* aConditions);
+
+ /**
+ * Compile a <where> tag in a condition. The caller should set
+ * *aCurrentCondition to null for the first condition. This value will be
+ * updated to point to the new condition before returning. The conditions
+ * will be added to the rule aRule by this method.
+ *
+ * @param aRule template rule
+ * @param aCondition <where> element
+ * @param aCurrentCondition compiled condition
+ */
+ nsresult
+ CompileWhereCondition(nsTemplateRule* aRule,
+ nsIContent* aCondition,
+ nsTemplateCondition** aCurrentCondition);
+
+ /**
+ * Compile the <bindings> for an extended template syntax rule.
+ */
+ nsresult
+ CompileBindings(nsTemplateRule* aRule, nsIContent* aBindings);
+
+ /**
+ * Compile a single binding for an extended template syntax rule.
+ */
+ nsresult
+ CompileBinding(nsTemplateRule* aRule, nsIContent* aBinding);
+
+ /**
+ * Add automatic bindings for simple rules
+ */
+ nsresult
+ AddSimpleRuleBindings(nsTemplateRule* aRule, nsIContent* aElement);
+
+ static void
+ AddBindingsFor(nsXULTemplateBuilder* aSelf,
+ const nsAString& aVariable,
+ void* aClosure);
+
+ /**
+ * Load the datasources for the template. shouldDelayBuilding is an out
+ * parameter which will be set to true to indicate that content building
+ * should not be performed yet as the datasource has not yet loaded. If
+ * false, the datasource has already loaded so building can proceed
+ * immediately. In the former case, the datasource or query processor
+ * should either rebuild the template or update results when the
+ * datasource is loaded as needed.
+ */
+ nsresult
+ LoadDataSources(nsIDocument* aDoc, bool* shouldDelayBuilding);
+
+ /**
+ * Called by LoadDataSources to load a datasource given a uri list
+ * in aDataSource. The list is a set of uris separated by spaces.
+ * If aIsRDFQuery is true, then this is for an RDF datasource which
+ * causes the method to check for additional flags specific to the
+ * RDF processor.
+ */
+ nsresult
+ LoadDataSourceUrls(nsIDocument* aDocument,
+ const nsAString& aDataSources,
+ bool aIsRDFQuery,
+ bool* aShouldDelayBuilding);
+
+ nsresult
+ InitHTMLTemplateRoot();
+
+ /**
+ * Determine which rule matches a given result. aContainer is used for
+ * tag matching and is optional for non-content generating builders.
+ * The returned matched rule is always one of the rules owned by the
+ * query set aQuerySet.
+ *
+ * @param aContainer parent where generated content will be inserted
+ * @param aResult result to match
+ * @param aQuerySet query set to examine the rules of
+ * @param aMatchedRule [out] rule that has matched, or null if any.
+ * @param aRuleIndex [out] index of the rule
+ */
+ nsresult
+ DetermineMatchedRule(nsIContent* aContainer,
+ nsIXULTemplateResult* aResult,
+ nsTemplateQuerySet* aQuerySet,
+ nsTemplateRule** aMatchedRule,
+ int16_t *aRuleIndex);
+
+ // XXX sigh, the string template foo doesn't mix with
+ // operator->*() on egcs-1.1.2, so we'll need to explicitly pass
+ // "this" and use good ol' fashioned static callbacks.
+ void
+ ParseAttribute(const nsAString& aAttributeValue,
+ void (*aVariableCallback)(nsXULTemplateBuilder* aThis, const nsAString&, void*),
+ void (*aTextCallback)(nsXULTemplateBuilder* aThis, const nsAString&, void*),
+ void* aClosure);
+
+ nsresult
+ SubstituteText(nsIXULTemplateResult* aMatch,
+ const nsAString& aAttributeValue,
+ nsAString& aResult);
+
+ static void
+ SubstituteTextAppendText(nsXULTemplateBuilder* aThis, const nsAString& aText, void* aClosure);
+
+ static void
+ SubstituteTextReplaceVariable(nsXULTemplateBuilder* aThis, const nsAString& aVariable, void* aClosure);
+
+ nsresult
+ IsSystemPrincipal(nsIPrincipal *principal, bool *result);
+
+ /**
+ * Convenience method which gets a resource for a result. If a result
+ * doesn't have a resource set, it will create one from the result's id.
+ */
+ nsresult GetResultResource(nsIXULTemplateResult* aResult,
+ nsIRDFResource** aResource);
+
+protected:
+ virtual ~nsXULTemplateBuilder();
+
+ nsCOMPtr<nsISupports> mDataSource;
+ nsCOMPtr<nsIRDFDataSource> mDB;
+ nsCOMPtr<nsIRDFCompositeDataSource> mCompDB;
+
+ /**
+ * Circular reference, broken when the document is destroyed.
+ */
+ nsCOMPtr<nsIContent> mRoot;
+
+ /**
+ * The root result, translated from the root element's ref
+ */
+ nsCOMPtr<nsIXULTemplateResult> mRootResult;
+
+ nsCOMArray<nsIXULBuilderListener> mListeners;
+
+ /**
+ * The query processor which generates results
+ */
+ nsCOMPtr<nsIXULTemplateQueryProcessor> mQueryProcessor;
+
+ /**
+ * The list of querysets
+ */
+ nsTArray<nsTemplateQuerySet *> mQuerySets;
+
+ /**
+ * Set to true if the rules have already been compiled
+ */
+ bool mQueriesCompiled;
+
+ /**
+ * The default reference and member variables.
+ */
+ nsCOMPtr<nsIAtom> mRefVariable;
+ nsCOMPtr<nsIAtom> mMemberVariable;
+
+ /**
+ * The match map contains nsTemplateMatch objects, one for each unique
+ * match found, keyed by the resource for that match. A particular match
+ * will contain a linked list of all of the matches for that unique result
+ * id. Only one is active at a time. When a match is retracted, look in
+ * the match map, remove it, and apply the next valid match in sequence to
+ * make active.
+ */
+ nsDataHashtable<nsISupportsHashKey, nsTemplateMatch*> mMatchMap;
+
+ // pseudo-constants
+ static nsrefcnt gRefCnt;
+ static nsIRDFService* gRDFService;
+ static nsIRDFContainerUtils* gRDFContainerUtils;
+ static nsIScriptSecurityManager* gScriptSecurityManager;
+ static nsIPrincipal* gSystemPrincipal;
+ static nsIObserverService* gObserverService;
+
+ enum {
+ eDontTestEmpty = (1 << 0),
+ eDontRecurse = (1 << 1),
+ eLoggingEnabled = (1 << 2)
+ };
+
+ int32_t mFlags;
+
+ /**
+ * Stack-based helper class to maintain a list of ``activated''
+ * resources; i.e., resources for which we are currently building
+ * content.
+ */
+ class ActivationEntry {
+ public:
+ nsIRDFResource *mResource;
+ ActivationEntry *mPrevious;
+ ActivationEntry **mLink;
+
+ ActivationEntry(nsIRDFResource *aResource, ActivationEntry **aLink)
+ : mResource(aResource),
+ mPrevious(*aLink),
+ mLink(aLink) { *mLink = this; }
+
+ ~ActivationEntry() { *mLink = mPrevious; }
+ };
+
+ /**
+ * The top of the stack of resources that we're currently building
+ * content for.
+ */
+ ActivationEntry *mTop;
+
+ /**
+ * Determine if a resource is currently on the activation stack.
+ */
+ bool
+ IsActivated(nsIRDFResource *aResource);
+
+ /**
+ * Returns true if content may be generated for a result, or false if it
+ * cannot, for example, if it would be created inside a closed container.
+ * Those results will be generated when the container is opened.
+ * If false is returned, no content should be generated. Possible
+ * insertion locations may optionally be set for new content, depending on
+ * the builder being used. Note that *aLocations or some items within
+ * aLocations may be null.
+ */
+ virtual bool
+ GetInsertionLocations(nsIXULTemplateResult* aResult,
+ nsCOMArray<nsIContent>** aLocations) = 0;
+
+ /**
+ * Must be implemented by subclasses. Handle removing the generated
+ * output for aOldMatch and adding new output for aNewMatch. Either
+ * aOldMatch or aNewMatch may be null. aContext is the location returned
+ * from the call to MayGenerateResult.
+ */
+ virtual nsresult
+ ReplaceMatch(nsIXULTemplateResult* aOldResult,
+ nsTemplateMatch* aNewMatch,
+ nsTemplateRule* aNewMatchRule,
+ void *aContext) = 0;
+
+ /**
+ * Must be implemented by subclasses. Handle change in bound
+ * variable values for aResult. aModifiedVars contains the set
+ * 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,
+ bool aIsNew);
+
+ virtual void Traverse(nsCycleCollectionTraversalCallback &cb) const
+ {
+ }
+
+ /**
+ * Start observing events from the observer service and the given
+ * document.
+ *
+ * @param aDocument the document to observe
+ */
+ void StartObserving(nsIDocument* aDocument);
+
+ /**
+ * Stop observing events from the observer service and any associated
+ * document.
+ */
+ void StopObserving();
+
+ /**
+ * Document that we're observing. Weak ref!
+ */
+ nsIDocument* mObservedDocument;
+};
+
+#endif // nsXULTemplateBuilder_h__
diff --git a/dom/xul/templates/nsXULTemplateQueryProcessorRDF.cpp b/dom/xul/templates/nsXULTemplateQueryProcessorRDF.cpp
new file mode 100644
index 000000000..732e545d0
--- /dev/null
+++ b/dom/xul/templates/nsXULTemplateQueryProcessorRDF.cpp
@@ -0,0 +1,1825 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsICollation.h"
+#include "nsIDOMNode.h"
+#include "nsIRDFNode.h"
+#include "nsIRDFObserver.h"
+#include "nsIRDFRemoteDataSource.h"
+#include "nsIRDFInferDataSource.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "nsIServiceManager.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsIDOMDocument.h"
+#include "nsAttrName.h"
+#include "rdf.h"
+#include "nsArrayUtils.h"
+#include "nsIURI.h"
+
+#include "nsContentTestNode.h"
+#include "nsRDFConInstanceTestNode.h"
+#include "nsRDFConMemberTestNode.h"
+#include "nsRDFPropertyTestNode.h"
+#include "nsInstantiationNode.h"
+#include "nsRDFTestNode.h"
+#include "nsXULContentUtils.h"
+#include "nsXULTemplateBuilder.h"
+#include "nsXULTemplateResultRDF.h"
+#include "nsXULTemplateResultSetRDF.h"
+#include "nsXULTemplateQueryProcessorRDF.h"
+#include "nsXULSortService.h"
+#include "nsIDocument.h"
+
+//----------------------------------------------------------------------
+
+#define PARSE_TYPE_INTEGER "Integer"
+
+nsrefcnt nsXULTemplateQueryProcessorRDF::gRefCnt = 0;
+nsIRDFService* nsXULTemplateQueryProcessorRDF::gRDFService;
+nsIRDFContainerUtils* nsXULTemplateQueryProcessorRDF::gRDFContainerUtils;
+nsIRDFResource* nsXULTemplateQueryProcessorRDF::kNC_BookmarkSeparator;
+nsIRDFResource* nsXULTemplateQueryProcessorRDF::kRDF_type;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULTemplateQueryProcessorRDF)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULTemplateQueryProcessorRDF)
+ tmp->Done();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULTemplateQueryProcessorRDF)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDB)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLastRef)
+
+ for (auto it = tmp->mBindingDependencies.Iter(); !it.Done(); it.Next()) {
+ nsXULTemplateQueryProcessorRDF::ResultArray* array = it.UserData();
+ int32_t count = array->Length();
+ for (int32_t i = 0; i < count; ++i) {
+ cb.NoteXPCOMChild(array->ElementAt(i));
+ }
+ }
+
+ for (auto it = tmp->mMemoryElementToResultMap.Iter();
+ !it.Done();
+ it.Next()) {
+ nsCOMArray<nsXULTemplateResultRDF>* array = it.UserData();
+ int32_t count = array->Count();
+ for (int32_t i = 0; i < count; ++i) {
+ cb.NoteXPCOMChild(array->ObjectAt(i));
+ }
+ }
+
+ for (auto it = tmp->mRuleToBindingsMap.Iter(); !it.Done(); it.Next()) {
+ cb.NoteXPCOMChild(it.Key());
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueries)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULTemplateQueryProcessorRDF)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULTemplateQueryProcessorRDF)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULTemplateQueryProcessorRDF)
+ NS_INTERFACE_MAP_ENTRY(nsIXULTemplateQueryProcessor)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFObserver)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXULTemplateQueryProcessor)
+NS_INTERFACE_MAP_END
+
+nsXULTemplateQueryProcessorRDF::nsXULTemplateQueryProcessorRDF(void)
+ : mDB(nullptr),
+ mBuilder(nullptr),
+ mQueryProcessorRDFInited(false),
+ mGenerationStarted(false),
+ mUpdateBatchNest(0),
+ mSimpleRuleMemberTest(nullptr)
+{
+ gRefCnt++;
+}
+
+nsXULTemplateQueryProcessorRDF::~nsXULTemplateQueryProcessorRDF(void)
+{
+ if (--gRefCnt == 0) {
+ NS_IF_RELEASE(gRDFService);
+ NS_IF_RELEASE(gRDFContainerUtils);
+ NS_IF_RELEASE(kNC_BookmarkSeparator);
+ NS_IF_RELEASE(kRDF_type);
+ }
+}
+
+nsresult
+nsXULTemplateQueryProcessorRDF::InitGlobals()
+{
+ nsresult rv;
+
+ // Initialize the global shared reference to the service
+ // manager and get some shared resource objects.
+ if (!gRDFService) {
+ NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+ rv = CallGetService(kRDFServiceCID, &gRDFService);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ if (!gRDFContainerUtils) {
+ NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID);
+ rv = CallGetService(kRDFContainerUtilsCID, &gRDFContainerUtils);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ if (!kNC_BookmarkSeparator) {
+ gRDFService->GetResource(
+ NS_LITERAL_CSTRING(NC_NAMESPACE_URI "BookmarkSeparator"),
+ &kNC_BookmarkSeparator);
+ }
+
+ if (!kRDF_type) {
+ gRDFService->GetResource(
+ NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "type"),
+ &kRDF_type);
+ }
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+//
+// nsIXULTemplateQueryProcessor interface
+//
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorRDF::GetDatasource(nsIArray* aDataSources,
+ nsIDOMNode* aRootNode,
+ bool aIsTrusted,
+ nsIXULTemplateBuilder* aBuilder,
+ bool* aShouldDelayBuilding,
+ nsISupports** aResult)
+{
+ nsCOMPtr<nsIRDFCompositeDataSource> compDB;
+ nsCOMPtr<nsIContent> root = do_QueryInterface(aRootNode);
+ nsresult rv;
+
+ *aResult = nullptr;
+ *aShouldDelayBuilding = false;
+
+ NS_ENSURE_TRUE(root, NS_ERROR_UNEXPECTED);
+
+ // make sure the RDF service is set up
+ rv = InitGlobals();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // create a database for the builder
+ compDB = do_CreateInstance(NS_RDF_DATASOURCE_CONTRACTID_PREFIX
+ "composite-datasource");
+ if (!compDB) {
+ NS_ERROR("unable to construct new composite data source");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // check for magical attributes. XXX move to ``flags''?
+ if (root->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::coalesceduplicatearcs,
+ nsGkAtoms::_false, eCaseMatters))
+ compDB->SetCoalesceDuplicateArcs(false);
+
+ if (root->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::allownegativeassertions,
+ nsGkAtoms::_false, eCaseMatters))
+ compDB->SetAllowNegativeAssertions(false);
+
+ if (aIsTrusted) {
+ // If we're a privileged (e.g., chrome) document, then add the
+ // local store as the first data source in the db. Note that
+ // we _might_ not be able to get a local store if we haven't
+ // got a profile to read from yet.
+ nsCOMPtr<nsIRDFDataSource> localstore;
+ rv = gRDFService->GetDataSource("rdf:local-store",
+ getter_AddRefs(localstore));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = compDB->AddDataSource(localstore);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to add local store to db");
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ uint32_t length, index;
+ rv = aDataSources->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ for (index = 0; index < length; index++) {
+
+ nsCOMPtr<nsIURI> uri = do_QueryElementAt(aDataSources, index);
+ if (!uri) // we ignore other datasources than uri
+ continue;
+
+ nsCOMPtr<nsIRDFDataSource> ds;
+ nsAutoCString uristrC;
+ uri->GetSpec(uristrC);
+
+ rv = gRDFService->GetDataSource(uristrC.get(), getter_AddRefs(ds));
+
+ if (NS_FAILED(rv)) {
+ // This is only a warning because the data source may not
+ // be accessible for any number of reasons, including
+ // security, a bad URL, etc.
+ #ifdef DEBUG
+ nsAutoCString msg;
+ msg.AppendLiteral("unable to load datasource '");
+ msg.Append(uristrC);
+ msg.Append('\'');
+ NS_WARNING(msg.get());
+ #endif
+ continue;
+ }
+
+ compDB->AddDataSource(ds);
+ }
+
+
+ // check if we were given an inference engine type
+ nsAutoString infer;
+ nsCOMPtr<nsIRDFDataSource> db;
+ root->GetAttr(kNameSpaceID_None, nsGkAtoms::infer, infer);
+ if (!infer.IsEmpty()) {
+ nsCString inferCID(NS_RDF_INFER_DATASOURCE_CONTRACTID_PREFIX);
+ AppendUTF16toUTF8(infer, inferCID);
+ nsCOMPtr<nsIRDFInferDataSource> inferDB =
+ do_CreateInstance(inferCID.get());
+
+ if (inferDB) {
+ inferDB->SetBaseDataSource(compDB);
+ db = do_QueryInterface(inferDB);
+ }
+ else {
+ NS_WARNING("failed to construct inference engine specified on template");
+ }
+ }
+
+ if (!db)
+ db = compDB;
+
+ return CallQueryInterface(db, aResult);
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorRDF::InitializeForBuilding(nsISupports* aDatasource,
+ nsIXULTemplateBuilder* aBuilder,
+ nsIDOMNode* aRootNode)
+{
+ if (!mQueryProcessorRDFInited) {
+ nsresult rv = InitGlobals();
+ if (NS_FAILED(rv))
+ return rv;
+
+ mQueryProcessorRDFInited = true;
+ }
+
+ // don't do anything if generation has already been done
+ if (mGenerationStarted)
+ return NS_ERROR_UNEXPECTED;
+
+ mDB = do_QueryInterface(aDatasource);
+ mBuilder = aBuilder;
+
+ ComputeContainmentProperties(aRootNode);
+
+ // Add ourselves as a datasource observer
+ if (mDB)
+ mDB->AddObserver(this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorRDF::Done()
+{
+ if (!mQueryProcessorRDFInited)
+ return NS_OK;
+
+ if (mDB)
+ mDB->RemoveObserver(this);
+
+ mDB = nullptr;
+ mBuilder = nullptr;
+ mRefVariable = nullptr;
+ mLastRef = nullptr;
+
+ mGenerationStarted = false;
+ mUpdateBatchNest = 0;
+
+ mContainmentProperties.Clear();
+
+ for (ReteNodeSet::Iterator node = mAllTests.First();
+ node != mAllTests.Last(); ++node)
+ delete *node;
+
+ mAllTests.Clear();
+ mRDFTests.Clear();
+ mQueries.Clear();
+
+ mSimpleRuleMemberTest = nullptr;
+
+ mBindingDependencies.Clear();
+
+ mMemoryElementToResultMap.Clear();
+
+ mRuleToBindingsMap.Clear();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorRDF::CompileQuery(nsIXULTemplateBuilder* aBuilder,
+ nsIDOMNode* aQueryNode,
+ nsIAtom* aRefVariable,
+ nsIAtom* aMemberVariable,
+ nsISupports** _retval)
+{
+ RefPtr<nsRDFQuery> query = new nsRDFQuery(this);
+ if (!query)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ query->mRefVariable = aRefVariable;
+ if (!mRefVariable)
+ mRefVariable = aRefVariable;
+
+ if (!aMemberVariable)
+ query->mMemberVariable = NS_Atomize("?");
+ else
+ query->mMemberVariable = aMemberVariable;
+
+ nsresult rv;
+ TestNode *lastnode = nullptr;
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aQueryNode);
+
+ if (content->NodeInfo()->Equals(nsGkAtoms::_template, kNameSpaceID_XUL)) {
+ // simplified syntax with no rules
+
+ query->SetSimple();
+ NS_ASSERTION(!mSimpleRuleMemberTest,
+ "CompileQuery called twice with the same template");
+ if (!mSimpleRuleMemberTest)
+ rv = CompileSimpleQuery(query, content, &lastnode);
+ else
+ rv = NS_ERROR_FAILURE;
+ }
+ else if (content->NodeInfo()->Equals(nsGkAtoms::rule, kNameSpaceID_XUL)) {
+ // simplified syntax with at least one rule
+ query->SetSimple();
+ rv = CompileSimpleQuery(query, content, &lastnode);
+ }
+ else {
+ rv = CompileExtendedQuery(query, content, &lastnode);
+ }
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ query->SetQueryNode(aQueryNode);
+
+ nsInstantiationNode* instnode = new nsInstantiationNode(this, query);
+
+ // this and other functions always add nodes to mAllTests first. That
+ // way if something fails, the node will just sit harmlessly in mAllTests
+ // where it can be deleted later.
+ rv = mAllTests.Add(instnode);
+ if (NS_FAILED(rv)) {
+ delete instnode;
+ return rv;
+ }
+
+ rv = lastnode->AddChild(instnode);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mQueries.AppendElement(query);
+
+ query.forget(_retval);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorRDF::GenerateResults(nsISupports* aDatasource,
+ nsIXULTemplateResult* aRef,
+ nsISupports* aQuery,
+ nsISimpleEnumerator** aResults)
+{
+ nsCOMPtr<nsITemplateRDFQuery> rdfquery = do_QueryInterface(aQuery);
+ if (! rdfquery)
+ return NS_ERROR_INVALID_ARG;
+
+ mGenerationStarted = true;
+
+ // should be safe to cast here since the query is a
+ // non-scriptable nsITemplateRDFQuery
+ nsRDFQuery* query = static_cast<nsRDFQuery *>(aQuery);
+
+ *aResults = nullptr;
+
+ nsCOMPtr<nsISimpleEnumerator> results;
+
+ if (aRef) {
+ // make sure that cached results were generated for this ref, and if not,
+ // regenerate them. Otherwise, things will go wrong for templates bound to
+ // an HTML element as they are not generated lazily.
+ if (aRef == mLastRef) {
+ query->UseCachedResults(getter_AddRefs(results));
+ }
+ else {
+ // clear the cached results
+ int32_t count = mQueries.Length();
+ for (int32_t r = 0; r < count; r++) {
+ mQueries[r]->ClearCachedResults();
+ }
+ }
+
+ if (! results) {
+ if (! query->mRefVariable)
+ query->mRefVariable = NS_Atomize("?uri");
+
+ nsCOMPtr<nsIRDFResource> refResource;
+ aRef->GetResource(getter_AddRefs(refResource));
+ if (! refResource)
+ return NS_ERROR_FAILURE;
+
+ // Propagate the assignments through the network
+ TestNode* root = query->GetRoot();
+
+ if (query->IsSimple() && mSimpleRuleMemberTest) {
+ // get the top node in the simple rule tree
+ root = mSimpleRuleMemberTest->GetParent();
+ mLastRef = aRef;
+ }
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ nsAutoString id;
+ aRef->GetId(id);
+
+ nsAutoString rvar;
+ query->mRefVariable->ToString(rvar);
+ nsAutoString mvar;
+ query->mMemberVariable->ToString(mvar);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("QueryProcessor::GenerateResults using ref %s and vars [ ref: %s member: %s]",
+ NS_ConvertUTF16toUTF8(id).get(),
+ NS_ConvertUTF16toUTF8(rvar).get(),
+ NS_ConvertUTF16toUTF8(mvar).get()));
+ }
+
+ if (root) {
+ // the seed is the initial instantiation, which has a single
+ // assignment holding the reference point
+ Instantiation seed;
+ seed.AddAssignment(query->mRefVariable, refResource);
+
+ InstantiationSet* instantiations = new InstantiationSet();
+ instantiations->Append(seed);
+
+ // if the propagation caused a match, then the results will be
+ // cached in the query, retrieved below by calling
+ // UseCachedResults. The matching result set owns the
+ // instantiations and will delete them when results have been
+ // iterated over. If the propagation did not match, the
+ // instantiations need to be deleted.
+ bool owned = false;
+ nsresult rv = root->Propagate(*instantiations, false, owned);
+ if (! owned)
+ delete instantiations;
+ if (NS_FAILED(rv))
+ return rv;
+
+ query->UseCachedResults(getter_AddRefs(results));
+ }
+ }
+ }
+
+ if (! results) {
+ // no results were found so create an empty set
+ results = new nsXULTemplateResultSetRDF(this, query, nullptr);
+ }
+
+ results.swap(*aResults);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorRDF::AddBinding(nsIDOMNode* aRuleNode,
+ nsIAtom* aVar,
+ nsIAtom* aRef,
+ const nsAString& aExpr)
+{
+ // add a <binding> to a rule. When a result is matched, the bindings are
+ // examined to add additional variable assignments
+
+ // bindings can't be added once result generation has started, otherwise
+ // the array sizes will get out of sync
+ if (mGenerationStarted)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIRDFResource> property;
+ nsresult rv = gRDFService->GetUnicodeResource(aExpr, getter_AddRefs(property));
+ if (NS_FAILED(rv))
+ return rv;
+
+ RefPtr<RDFBindingSet> bindings = mRuleToBindingsMap.GetWeak(aRuleNode);
+ if (!bindings) {
+ bindings = new RDFBindingSet();
+ mRuleToBindingsMap.Put(aRuleNode, bindings);
+ }
+
+ return bindings->AddBinding(aVar, aRef, property);
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorRDF::TranslateRef(nsISupports* aDatasource,
+ const nsAString& aRefString,
+ nsIXULTemplateResult** aRef)
+{
+ // make sure the RDF service is set up
+ nsresult rv = InitGlobals();
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIRDFResource> uri;
+ gRDFService->GetUnicodeResource(aRefString, getter_AddRefs(uri));
+
+ RefPtr<nsXULTemplateResultRDF> refresult = new nsXULTemplateResultRDF(uri);
+ if (! refresult)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ refresult.forget(aRef);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorRDF::CompareResults(nsIXULTemplateResult* aLeft,
+ nsIXULTemplateResult* aRight,
+ nsIAtom* aVar,
+ uint32_t aSortHints,
+ int32_t* aResult)
+{
+ NS_ENSURE_ARG_POINTER(aLeft);
+ NS_ENSURE_ARG_POINTER(aRight);
+
+ *aResult = 0;
+
+ // for natural order sorting, use the index in the RDF container for the
+ // order. If there is no container, just sort them arbitrarily.
+ if (!aVar) {
+ // if a result has a negative index, just sort it first
+ int32_t leftindex = GetContainerIndexOf(aLeft);
+ int32_t rightindex = GetContainerIndexOf(aRight);
+ *aResult = leftindex == rightindex ? 0 :
+ leftindex > rightindex ? 1 :
+ -1;
+ return NS_OK;
+ }
+
+ nsDependentAtomString sortkey(aVar);
+
+ nsCOMPtr<nsISupports> leftNode, rightNode;
+
+ if (!sortkey.IsEmpty() && sortkey[0] != '?' &&
+ !StringBeginsWith(sortkey, NS_LITERAL_STRING("rdf:")) &&
+ mDB) {
+ // if the sort key is not a template variable, it should be an RDF
+ // predicate. Get the targets and compare those instead.
+ nsCOMPtr<nsIRDFResource> predicate;
+ nsresult rv = gRDFService->GetUnicodeResource(sortkey, getter_AddRefs(predicate));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // create a predicate with '?sort=true' appended. This special
+ // predicate may be used to have a different sort value than the
+ // displayed value
+ sortkey.AppendLiteral("?sort=true");
+
+ nsCOMPtr<nsIRDFResource> sortPredicate;
+ rv = gRDFService->GetUnicodeResource(sortkey, getter_AddRefs(sortPredicate));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetSortValue(aLeft, predicate, sortPredicate, getter_AddRefs(leftNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetSortValue(aRight, predicate, sortPredicate, getter_AddRefs(rightNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ // get the values for the sort key from the results
+ aLeft->GetBindingObjectFor(aVar, getter_AddRefs(leftNode));
+ aRight->GetBindingObjectFor(aVar, getter_AddRefs(rightNode));
+ }
+
+ {
+ // Literals?
+ nsCOMPtr<nsIRDFLiteral> l = do_QueryInterface(leftNode);
+ if (l) {
+ nsCOMPtr<nsIRDFLiteral> r = do_QueryInterface(rightNode);
+ if (r) {
+ const char16_t *lstr, *rstr;
+ l->GetValueConst(&lstr);
+ r->GetValueConst(&rstr);
+
+ *aResult = XULSortServiceImpl::CompareValues(
+ nsDependentString(lstr),
+ nsDependentString(rstr), aSortHints);
+ }
+
+ return NS_OK;
+ }
+ }
+
+ {
+ // Dates?
+ nsCOMPtr<nsIRDFDate> l = do_QueryInterface(leftNode);
+ if (l) {
+ nsCOMPtr<nsIRDFDate> r = do_QueryInterface(rightNode);
+ if (r) {
+ PRTime ldate, rdate;
+ l->GetValue(&ldate);
+ r->GetValue(&rdate);
+
+ int64_t delta = ldate - rdate;
+ if (delta == 0)
+ *aResult = 0;
+ else if (delta >= 0)
+ *aResult = 1;
+ else
+ *aResult = -1;
+ }
+
+ return NS_OK;
+ }
+ }
+
+ {
+ // Integers?
+ nsCOMPtr<nsIRDFInt> l = do_QueryInterface(leftNode);
+ if (l) {
+ nsCOMPtr<nsIRDFInt> r = do_QueryInterface(rightNode);
+ if (r) {
+ int32_t lval, rval;
+ l->GetValue(&lval);
+ r->GetValue(&rval);
+
+ *aResult = lval - rval;
+ }
+
+ return NS_OK;
+ }
+ }
+
+ nsICollation* collation = nsXULContentUtils::GetCollation();
+ if (collation) {
+ // Blobs? (We can only compare these reasonably if we have a
+ // collation object.)
+ nsCOMPtr<nsIRDFBlob> l = do_QueryInterface(leftNode);
+ if (l) {
+ nsCOMPtr<nsIRDFBlob> r = do_QueryInterface(rightNode);
+ if (r) {
+ const uint8_t *lval, *rval;
+ int32_t llen, rlen;
+ l->GetValue(&lval);
+ l->GetLength(&llen);
+ r->GetValue(&rval);
+ r->GetLength(&rlen);
+
+ collation->CompareRawSortKey(lval, llen, rval, rlen, aResult);
+ }
+ }
+ }
+
+ // if the results are none of the above, just pretend that they are equal
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+//
+// nsIRDFObserver interface
+//
+
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorRDF::OnAssert(nsIRDFDataSource* aDataSource,
+ nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+{
+ // Ignore updates if we're batching
+ if (mUpdateBatchNest)
+ return(NS_OK);
+
+ if (! mBuilder)
+ return NS_OK;
+
+ LOG("onassert", aSource, aProperty, aTarget);
+
+ Propagate(aSource, aProperty, aTarget);
+ SynchronizeAll(aSource, aProperty, nullptr, aTarget);
+ return NS_OK;
+}
+
+
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorRDF::OnUnassert(nsIRDFDataSource* aDataSource,
+ nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+{
+ // Ignore updates if we're batching
+ if (mUpdateBatchNest)
+ return NS_OK;
+
+ if (! mBuilder)
+ return NS_OK;
+
+ LOG("onunassert", aSource, aProperty, aTarget);
+
+ Retract(aSource, aProperty, aTarget);
+ SynchronizeAll(aSource, aProperty, aTarget, nullptr);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorRDF::OnChange(nsIRDFDataSource* aDataSource,
+ nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aOldTarget,
+ nsIRDFNode* aNewTarget)
+{
+ // Ignore updates if we're batching
+ if (mUpdateBatchNest)
+ return NS_OK;
+
+ if (! mBuilder)
+ return NS_OK;
+
+ LOG("onchange", aSource, aProperty, aNewTarget);
+
+ if (aOldTarget) {
+ // Pull any old results that were relying on aOldTarget
+ Retract(aSource, aProperty, aOldTarget);
+ }
+
+ if (aNewTarget) {
+ // Fire any new results that are activated by aNewTarget
+ Propagate(aSource, aProperty, aNewTarget);
+ }
+
+ // Synchronize any of the content model that may have changed.
+ SynchronizeAll(aSource, aProperty, aOldTarget, aNewTarget);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorRDF::OnMove(nsIRDFDataSource* aDataSource,
+ nsIRDFResource* aOldSource,
+ nsIRDFResource* aNewSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+{
+ // Ignore updates if we're batching
+ if (mUpdateBatchNest)
+ return NS_OK;
+
+ NS_NOTYETIMPLEMENTED("write me");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorRDF::OnBeginUpdateBatch(nsIRDFDataSource* aDataSource)
+{
+ mUpdateBatchNest++;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorRDF::OnEndUpdateBatch(nsIRDFDataSource* aDataSource)
+{
+ NS_ASSERTION(mUpdateBatchNest > 0, "badly nested update batch");
+ if (--mUpdateBatchNest <= 0) {
+ mUpdateBatchNest = 0;
+
+ if (mBuilder)
+ mBuilder->Rebuild();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateQueryProcessorRDF::Propagate(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+{
+ // When a new assertion is added to the graph, determine any new matches
+ // that must be added to the template builder. First, iterate through all
+ // the RDF tests (<member> and <triple> tests), and find the topmost test
+ // that would be affected by the new assertion.
+ nsresult rv;
+
+ ReteNodeSet livenodes;
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ const char* sourceStr;
+ aSource->GetValueConst(&sourceStr);
+ const char* propertyStr;
+ aProperty->GetValueConst(&propertyStr);
+ nsAutoString targetStr;
+ nsXULContentUtils::GetTextForNode(aTarget, targetStr);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsXULTemplateQueryProcessorRDF::Propagate: [%s] -> [%s] -> [%s]\n",
+ sourceStr, propertyStr, NS_ConvertUTF16toUTF8(targetStr).get()));
+ }
+
+ {
+ ReteNodeSet::Iterator last = mRDFTests.Last();
+ for (ReteNodeSet::Iterator i = mRDFTests.First(); i != last; ++i) {
+ nsRDFTestNode* rdftestnode = static_cast<nsRDFTestNode*>(*i);
+
+ Instantiation seed;
+ if (rdftestnode->CanPropagate(aSource, aProperty, aTarget, seed)) {
+ rv = livenodes.Add(rdftestnode);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ }
+ }
+
+ // Now, we'll go through each, and any that aren't dominated by
+ // another live node will be used to propagate the assertion
+ // through the rule network
+ {
+ ReteNodeSet::Iterator last = livenodes.Last();
+ for (ReteNodeSet::Iterator i = livenodes.First(); i != last; ++i) {
+ nsRDFTestNode* rdftestnode = static_cast<nsRDFTestNode*>(*i);
+
+ // What happens here is we create an instantiation as if we were
+ // at the found test in the rule network. For example, if the
+ // found test was a member test (parent => child), the parent
+ // and child variables are assigned the values provided by the new
+ // RDF assertion in the graph. The Constrain call is used to go
+ // up to earlier RDF tests, filling in variables as it goes.
+ // Constrain will eventually get up to the top node, an
+ // nsContentTestNode, which takes the value of the reference
+ // variable and calls the template builder to see if a result has
+ // been generated already for the reference value. If it hasn't,
+ // the new assertion couldn't cause a new match. If the result
+ // exists, call Propagate to continue to the later RDF tests to
+ // fill in the rest of the variable assignments.
+
+ // Bogus, to get the seed instantiation
+ Instantiation seed;
+ rdftestnode->CanPropagate(aSource, aProperty, aTarget, seed);
+
+ InstantiationSet* instantiations = new InstantiationSet();
+ instantiations->Append(seed);
+
+ rv = rdftestnode->Constrain(*instantiations);
+ if (NS_FAILED(rv)) {
+ delete instantiations;
+ return rv;
+ }
+
+ bool owned = false;
+ if (!instantiations->Empty())
+ rv = rdftestnode->Propagate(*instantiations, true, owned);
+
+ // owned should always be false in update mode, but check just
+ // to be sure
+ if (!owned)
+ delete instantiations;
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+nsXULTemplateQueryProcessorRDF::Retract(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+{
+
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ const char* sourceStr;
+ aSource->GetValueConst(&sourceStr);
+ const char* propertyStr;
+ aProperty->GetValueConst(&propertyStr);
+ nsAutoString targetStr;
+ nsXULContentUtils::GetTextForNode(aTarget, targetStr);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("nsXULTemplateQueryProcessorRDF::Retract: [%s] -> [%s] -> [%s]\n",
+ sourceStr, propertyStr, NS_ConvertUTF16toUTF8(targetStr).get()));
+ }
+
+ // Retract any currently active rules that will no longer be matched.
+ ReteNodeSet::ConstIterator lastnode = mRDFTests.Last();
+ for (ReteNodeSet::ConstIterator node = mRDFTests.First(); node != lastnode; ++node) {
+ const nsRDFTestNode* rdftestnode = static_cast<const nsRDFTestNode*>(*node);
+
+ rdftestnode->Retract(aSource, aProperty, aTarget);
+
+ // Now fire any newly revealed rules
+ // XXXwaterson yo. write me.
+ // The intent here is to handle any rules that might be
+ // "revealed" by the removal of an assertion from the datasource.
+ // Waterson doesn't think we support negated conditions in a rule.
+ // Nor is he sure that this is currently useful.
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateQueryProcessorRDF::SynchronizeAll(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aOldTarget,
+ nsIRDFNode* aNewTarget)
+{
+ // Update each match that contains <aSource, aProperty, aOldTarget>.
+
+ // Get all the matches whose assignments are currently supported
+ // by aSource and aProperty: we'll need to recompute them.
+ ResultArray* results;
+ if (!mBindingDependencies.Get(aSource, &results) || !mBuilder)
+ return NS_OK;
+
+ uint32_t length = results->Length();
+
+ for (uint32_t r = 0; r < length; r++) {
+ nsXULTemplateResultRDF* result = (*results)[r];
+ if (result) {
+ // synchronize the result's bindings and then update the builder
+ // so that content can be updated
+ if (result->SyncAssignments(aSource, aProperty, aNewTarget)) {
+ nsITemplateRDFQuery* query = result->Query();
+ if (query) {
+ nsCOMPtr<nsIDOMNode> querynode;
+ query->GetQueryNode(getter_AddRefs(querynode));
+
+ mBuilder->ResultBindingChanged(result);
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateQueryProcessorRDF::Log(const char* aOperation,
+ nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+{
+ if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Debug)) {
+ nsresult rv;
+
+ const char* sourceStr;
+ rv = aSource->GetValueConst(&sourceStr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("xultemplate[%p] %8s [%s]--", this, aOperation, sourceStr));
+
+ const char* propertyStr;
+ rv = aProperty->GetValueConst(&propertyStr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoString targetStr;
+ rv = nsXULContentUtils::GetTextForNode(aTarget, targetStr);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString targetstrC;
+ targetstrC.AssignWithConversion(targetStr);
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ (" --[%s]-->[%s]",
+ propertyStr,
+ targetstrC.get()));
+ }
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateQueryProcessorRDF::CheckContainer(nsIRDFResource* aResource,
+ bool* aIsContainer)
+{
+ NS_ENSURE_ARG_POINTER(aIsContainer);
+ NS_ENSURE_STATE(mDB);
+
+ // We have to look at all of the arcs extending out of the
+ // resource: if any of them are that "containment" property, then
+ // we know we'll have children.
+ bool isContainer = false;
+
+ for (nsResourceSet::ConstIterator property = mContainmentProperties.First();
+ property != mContainmentProperties.Last();
+ property++) {
+ bool hasArc = false;
+ mDB->HasArcOut(aResource, *property, &hasArc);
+
+ if (hasArc) {
+ // Well, it's a container...
+ isContainer = true;
+ break;
+ }
+ }
+
+ // If we get here, and we're still not sure if it's a container,
+ // then see if it's an RDF container
+ if (! isContainer) {
+ gRDFContainerUtils->IsContainer(mDB, aResource, &isContainer);
+ }
+
+ *aIsContainer = isContainer;
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateQueryProcessorRDF::CheckEmpty(nsIRDFResource* aResource,
+ bool* aIsEmpty)
+{
+ NS_ENSURE_STATE(mDB);
+ *aIsEmpty = true;
+
+ for (nsResourceSet::ConstIterator property = mContainmentProperties.First();
+ property != mContainmentProperties.Last();
+ property++) {
+
+ nsCOMPtr<nsIRDFNode> dummy;
+ mDB->GetTarget(aResource, *property, true, getter_AddRefs(dummy));
+
+ if (dummy) {
+ *aIsEmpty = false;
+ break;
+ }
+ }
+
+ if (*aIsEmpty){
+ return nsXULTemplateQueryProcessorRDF::gRDFContainerUtils->
+ IsEmpty(mDB, aResource, aIsEmpty);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateQueryProcessorRDF::CheckIsSeparator(nsIRDFResource* aResource,
+ bool* aIsSeparator)
+{
+ NS_ENSURE_STATE(mDB);
+ return mDB->HasAssertion(aResource, kRDF_type, kNC_BookmarkSeparator,
+ true, aIsSeparator);
+}
+
+//----------------------------------------------------------------------
+
+nsresult
+nsXULTemplateQueryProcessorRDF::ComputeContainmentProperties(nsIDOMNode* aRootNode)
+{
+ // The 'containment' attribute on the root node is a
+ // whitespace-separated list that tells us which properties we
+ // should use to test for containment.
+ nsresult rv;
+
+ mContainmentProperties.Clear();
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aRootNode);
+
+ nsAutoString containment;
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::containment, containment);
+
+ uint32_t len = containment.Length();
+ uint32_t offset = 0;
+ while (offset < len) {
+ while (offset < len && nsCRT::IsAsciiSpace(containment[offset]))
+ ++offset;
+
+ if (offset >= len)
+ break;
+
+ uint32_t end = offset;
+ while (end < len && !nsCRT::IsAsciiSpace(containment[end]))
+ ++end;
+
+ nsAutoString propertyStr;
+ containment.Mid(propertyStr, offset, end - offset);
+
+ nsCOMPtr<nsIRDFResource> property;
+ rv = gRDFService->GetUnicodeResource(propertyStr, getter_AddRefs(property));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = mContainmentProperties.Add(property);
+ if (NS_FAILED(rv))
+ return rv;
+
+ offset = end;
+ }
+
+#define TREE_PROPERTY_HACK 1
+#if defined(TREE_PROPERTY_HACK)
+ if (! len) {
+ // Some ever-present membership tests.
+ mContainmentProperties.Add(nsXULContentUtils::NC_child);
+ mContainmentProperties.Add(nsXULContentUtils::NC_Folder);
+ }
+#endif
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateQueryProcessorRDF::CompileExtendedQuery(nsRDFQuery* aQuery,
+ nsIContent* aConditions,
+ TestNode** aLastNode)
+{
+ // Compile an extended query's children
+ nsContentTestNode* idnode =
+ new nsContentTestNode(this, aQuery->mRefVariable);
+
+ aQuery->SetRoot(idnode);
+ nsresult rv = mAllTests.Add(idnode);
+ if (NS_FAILED(rv)) {
+ delete idnode;
+ return rv;
+ }
+
+ TestNode* prevnode = idnode;
+
+ for (nsIContent* condition = aConditions->GetFirstChild();
+ condition;
+ condition = condition->GetNextSibling()) {
+
+ // the <content> condition should always be the first child
+ if (condition->IsXULElement(nsGkAtoms::content)) {
+ if (condition != aConditions->GetFirstChild()) {
+ 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 = NS_Atomize(tagstr);
+ }
+
+ nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(condition->GetComposedDoc());
+ if (! doc)
+ return NS_ERROR_FAILURE;
+
+ idnode->SetTag(tag, doc);
+ continue;
+ }
+
+ TestNode* testnode = nullptr;
+ nsresult rv = CompileQueryChild(condition->NodeInfo()->NameAtom(),
+ aQuery, condition, prevnode, &testnode);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (testnode) {
+ rv = prevnode->AddChild(testnode);
+ if (NS_FAILED(rv))
+ return rv;
+
+ prevnode = testnode;
+ }
+ }
+
+ *aLastNode = prevnode;
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateQueryProcessorRDF::CompileQueryChild(nsIAtom* aTag,
+ nsRDFQuery* aQuery,
+ nsIContent* aCondition,
+ TestNode* aParentNode,
+ TestNode** aResult)
+{
+ nsresult rv = NS_OK;
+
+ if (aTag == nsGkAtoms::triple) {
+ rv = CompileTripleCondition(aQuery, aCondition, aParentNode, aResult);
+ }
+ else if (aTag == nsGkAtoms::member) {
+ rv = CompileMemberCondition(aQuery, aCondition, aParentNode, aResult);
+ }
+ else if (MOZ_LOG_TEST(gXULTemplateLog, LogLevel::Info)) {
+ nsAutoString tagstr;
+ aTag->ToString(tagstr);
+
+ nsAutoCString tagstrC;
+ tagstrC.AssignWithConversion(tagstr);
+ MOZ_LOG(gXULTemplateLog, LogLevel::Info,
+ ("xultemplate[%p] unrecognized condition test <%s>",
+ this, tagstrC.get()));
+ }
+
+ return rv;
+}
+
+nsresult
+nsXULTemplateQueryProcessorRDF::ParseLiteral(const nsString& aParseType,
+ const nsString& aValue,
+ nsIRDFNode** aResult)
+{
+ nsresult rv = NS_OK;
+ *aResult = nullptr;
+
+ if (aParseType.EqualsLiteral(PARSE_TYPE_INTEGER)) {
+ nsCOMPtr<nsIRDFInt> intLiteral;
+ nsresult errorCode;
+ int32_t intValue = aValue.ToInteger(&errorCode);
+ if (NS_FAILED(errorCode))
+ return NS_ERROR_FAILURE;
+ rv = gRDFService->GetIntLiteral(intValue, getter_AddRefs(intLiteral));
+ if (NS_FAILED(rv))
+ return rv;
+ intLiteral.forget(aResult);
+ }
+ else {
+ nsCOMPtr<nsIRDFLiteral> literal;
+ rv = gRDFService->GetLiteral(aValue.get(), getter_AddRefs(literal));
+ if (NS_FAILED(rv))
+ return rv;
+ literal.forget(aResult);
+ }
+ return rv;
+}
+
+nsresult
+nsXULTemplateQueryProcessorRDF::CompileTripleCondition(nsRDFQuery* aQuery,
+ nsIContent* aCondition,
+ TestNode* aParentNode,
+ TestNode** aResult)
+{
+ // Compile a <triple> condition, which must be of the form:
+ //
+ // <triple subject="?var1|resource"
+ // predicate="resource"
+ // object="?var2|resource|literal" />
+ //
+ // XXXwaterson Some day it would be cool to allow the 'predicate'
+ // to be bound to a variable.
+
+ // subject
+ nsAutoString subject;
+ aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::subject, subject);
+
+ nsCOMPtr<nsIAtom> svar;
+ nsCOMPtr<nsIRDFResource> sres;
+ if (subject.IsEmpty()) {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_TRIPLE_BAD_SUBJECT);
+ return NS_OK;
+ }
+ if (subject[0] == char16_t('?'))
+ svar = NS_Atomize(subject);
+ else
+ gRDFService->GetUnicodeResource(subject, getter_AddRefs(sres));
+
+ // predicate
+ nsAutoString predicate;
+ aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::predicate, predicate);
+
+ nsCOMPtr<nsIRDFResource> pres;
+ if (predicate.IsEmpty() || predicate[0] == char16_t('?')) {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_TRIPLE_BAD_PREDICATE);
+ 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()) {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_TRIPLE_BAD_OBJECT);
+ return NS_OK;
+ }
+
+ if (object[0] == char16_t('?')) {
+ ovar = NS_Atomize(object);
+ }
+ else if (object.FindChar(':') != -1) { // XXXwaterson evil.
+ // treat as resource
+ nsCOMPtr<nsIRDFResource> resource;
+ gRDFService->GetUnicodeResource(object, getter_AddRefs(resource));
+ onode = do_QueryInterface(resource);
+ }
+ else {
+ nsAutoString parseType;
+ aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::parsetype, parseType);
+ nsresult rv = ParseLiteral(parseType, object, getter_AddRefs(onode));
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ nsRDFPropertyTestNode* testnode = nullptr;
+
+ if (svar && ovar) {
+ testnode = new nsRDFPropertyTestNode(aParentNode, this, svar, pres, ovar);
+ }
+ else if (svar) {
+ testnode = new nsRDFPropertyTestNode(aParentNode, this, svar, pres, onode);
+ }
+ else if (ovar) {
+ testnode = new nsRDFPropertyTestNode(aParentNode, this, sres, pres, ovar);
+ }
+ else {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_TRIPLE_NO_VAR);
+ return NS_OK;
+ }
+
+ // add testnode to mAllTests first. If adding to mRDFTests fails, just
+ // leave it in the list so that it can be deleted later.
+ MOZ_ASSERT(testnode);
+ nsresult rv = mAllTests.Add(testnode);
+ if (NS_FAILED(rv)) {
+ delete testnode;
+ return rv;
+ }
+
+ rv = mRDFTests.Add(testnode);
+ if (NS_FAILED(rv))
+ return rv;
+
+ *aResult = testnode;
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateQueryProcessorRDF::CompileMemberCondition(nsRDFQuery* aQuery,
+ nsIContent* aCondition,
+ TestNode* aParentNode,
+ TestNode** aResult)
+{
+ // Compile a <member> condition, which must be of the form:
+ //
+ // <member container="?var1" child="?var2" />
+ //
+
+ // container
+ nsAutoString container;
+ aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::container, container);
+
+ if (!container.IsEmpty() && container[0] != char16_t('?')) {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_MEMBER_NOCONTAINERVAR);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIAtom> containervar = NS_Atomize(container);
+
+ // child
+ nsAutoString child;
+ aCondition->GetAttr(kNameSpaceID_None, nsGkAtoms::child, child);
+
+ if (!child.IsEmpty() && child[0] != char16_t('?')) {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_MEMBER_NOCHILDVAR);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIAtom> childvar = NS_Atomize(child);
+
+ TestNode* testnode =
+ new nsRDFConMemberTestNode(aParentNode,
+ this,
+ containervar,
+ childvar);
+
+ // add testnode to mAllTests first. If adding to mRDFTests fails, just
+ // leave it in the list so that it can be deleted later.
+ nsresult rv = mAllTests.Add(testnode);
+ if (NS_FAILED(rv)) {
+ delete testnode;
+ return rv;
+ }
+
+ rv = mRDFTests.Add(testnode);
+ if (NS_FAILED(rv))
+ return rv;
+
+ *aResult = testnode;
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateQueryProcessorRDF::AddDefaultSimpleRules(nsRDFQuery* aQuery,
+ TestNode** aChildNode)
+{
+ // XXXndeakin should check for tag in query processor instead of builder?
+ nsContentTestNode* idnode =
+ new nsContentTestNode(this,
+ aQuery->mRefVariable);
+
+ // Create (?container ^member ?member)
+ nsRDFConMemberTestNode* membernode =
+ new nsRDFConMemberTestNode(idnode,
+ this,
+ aQuery->mRefVariable,
+ aQuery->mMemberVariable);
+
+ // add nodes to mAllTests first. If later calls fail, just leave them in
+ // the list so that they can be deleted later.
+ nsresult rv = mAllTests.Add(idnode);
+ if (NS_FAILED(rv)) {
+ delete idnode;
+ delete membernode;
+ return rv;
+ }
+
+ rv = mAllTests.Add(membernode);
+ if (NS_FAILED(rv)) {
+ delete membernode;
+ return rv;
+ }
+
+ rv = mRDFTests.Add(membernode);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = idnode->AddChild(membernode);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mSimpleRuleMemberTest = membernode;
+ *aChildNode = membernode;
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateQueryProcessorRDF::CompileSimpleQuery(nsRDFQuery* aQuery,
+ nsIContent* aQueryElement,
+ TestNode** aLastNode)
+{
+ // Compile a "simple" (or old-school style) <template> query.
+ nsresult rv;
+
+ TestNode* parentNode;
+
+ if (! mSimpleRuleMemberTest) {
+ rv = AddDefaultSimpleRules(aQuery, &parentNode);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ bool hasContainerTest = false;
+
+ TestNode* prevnode = mSimpleRuleMemberTest;
+
+ // Add constraints for the LHS
+ const nsAttrName* name;
+ for (uint32_t i = 0; (name = aQueryElement->GetAttrNameAt(i)); ++i) {
+ // Note: some attributes must be skipped on XUL template query subtree
+
+ // never compare against rdf:property, rdf:instanceOf, {}:id or {}:parsetype attribute
+ if (name->Equals(nsGkAtoms::property, kNameSpaceID_RDF) ||
+ name->Equals(nsGkAtoms::instanceOf, kNameSpaceID_RDF) ||
+ name->Equals(nsGkAtoms::id, kNameSpaceID_None) ||
+ name->Equals(nsGkAtoms::parsetype, kNameSpaceID_None)) {
+ continue;
+ }
+
+ int32_t attrNameSpaceID = name->NamespaceID();
+ if (attrNameSpaceID == kNameSpaceID_XMLNS)
+ continue;
+ nsIAtom* attr = name->LocalName();
+
+ nsAutoString value;
+ aQueryElement->GetAttr(attrNameSpaceID, attr, value);
+
+ TestNode* testnode = nullptr;
+
+ if (name->Equals(nsGkAtoms::iscontainer, kNameSpaceID_None) ||
+ name->Equals(nsGkAtoms::isempty, kNameSpaceID_None)) {
+ // Tests about containerhood and emptiness. These can be
+ // globbed together, mostly. Check to see if we've already
+ // added a container test: we only need one.
+ if (hasContainerTest)
+ continue;
+
+ nsRDFConInstanceTestNode::Test iscontainer =
+ nsRDFConInstanceTestNode::eDontCare;
+
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::_true, &nsGkAtoms::_false, nullptr};
+ switch (aQueryElement->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::iscontainer,
+ strings, eCaseMatters)) {
+ case 0: iscontainer = nsRDFConInstanceTestNode::eTrue; break;
+ case 1: iscontainer = nsRDFConInstanceTestNode::eFalse; break;
+ }
+
+ nsRDFConInstanceTestNode::Test isempty =
+ nsRDFConInstanceTestNode::eDontCare;
+
+ switch (aQueryElement->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::isempty,
+ strings, eCaseMatters)) {
+ case 0: isempty = nsRDFConInstanceTestNode::eTrue; break;
+ case 1: isempty = nsRDFConInstanceTestNode::eFalse; break;
+ }
+
+ testnode = new nsRDFConInstanceTestNode(prevnode,
+ this,
+ aQuery->mMemberVariable,
+ iscontainer,
+ isempty);
+
+ rv = mAllTests.Add(testnode);
+ if (NS_FAILED(rv)) {
+ delete testnode;
+ return rv;
+ }
+
+ rv = mRDFTests.Add(testnode);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ else if (attrNameSpaceID != kNameSpaceID_None || attr != nsGkAtoms::parent) {
+ // It's a simple RDF test
+ nsCOMPtr<nsIRDFResource> property;
+ rv = nsXULContentUtils::GetResource(attrNameSpaceID, attr, getter_AddRefs(property));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // XXXwaterson this is so manky
+ nsCOMPtr<nsIRDFNode> target;
+ if (value.FindChar(':') != -1) { // XXXwaterson WRONG WRONG WRONG!
+ nsCOMPtr<nsIRDFResource> resource;
+ rv = gRDFService->GetUnicodeResource(value, getter_AddRefs(resource));
+ if (NS_FAILED(rv))
+ return rv;
+
+ target = do_QueryInterface(resource);
+ }
+ else {
+ nsAutoString parseType;
+ aQueryElement->GetAttr(kNameSpaceID_None, nsGkAtoms::parsetype, parseType);
+ rv = ParseLiteral(parseType, value, getter_AddRefs(target));
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ testnode = new nsRDFPropertyTestNode(prevnode, this,
+ aQuery->mMemberVariable, property, target);
+ rv = mAllTests.Add(testnode);
+ if (NS_FAILED(rv)) {
+ delete testnode;
+ return rv;
+ }
+
+ rv = mRDFTests.Add(testnode);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ if (testnode) {
+ if (prevnode) {
+ rv = prevnode->AddChild(testnode);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ else {
+ aQuery->SetRoot(testnode);
+ }
+
+ prevnode = testnode;
+ }
+ }
+
+ *aLastNode = prevnode;
+
+ return NS_OK;
+}
+
+RDFBindingSet*
+nsXULTemplateQueryProcessorRDF::GetBindingsForRule(nsIDOMNode* aRuleNode)
+{
+ return mRuleToBindingsMap.GetWeak(aRuleNode);
+}
+
+void
+nsXULTemplateQueryProcessorRDF::AddBindingDependency(nsXULTemplateResultRDF* aResult,
+ nsIRDFResource* aResource)
+{
+ ResultArray* arr;
+ if (!mBindingDependencies.Get(aResource, &arr)) {
+ arr = new ResultArray();
+
+ mBindingDependencies.Put(aResource, arr);
+ }
+
+ int32_t index = arr->IndexOf(aResult);
+ if (index == -1)
+ arr->AppendElement(aResult);
+}
+
+void
+nsXULTemplateQueryProcessorRDF::RemoveBindingDependency(nsXULTemplateResultRDF* aResult,
+ nsIRDFResource* aResource)
+{
+ ResultArray* arr;
+ if (mBindingDependencies.Get(aResource, &arr)) {
+ int32_t index = arr->IndexOf(aResult);
+ if (index >= 0)
+ arr->RemoveElementAt(index);
+ }
+}
+
+
+nsresult
+nsXULTemplateQueryProcessorRDF::AddMemoryElements(const Instantiation& aInst,
+ nsXULTemplateResultRDF* aResult)
+{
+ // Add the result to a table indexed by supporting MemoryElement
+ MemoryElementSet::ConstIterator last = aInst.mSupport.Last();
+ for (MemoryElementSet::ConstIterator element = aInst.mSupport.First();
+ element != last; ++element) {
+
+ PLHashNumber hash = (element.operator->())->Hash();
+
+ nsCOMArray<nsXULTemplateResultRDF>* arr;
+ if (!mMemoryElementToResultMap.Get(hash, &arr)) {
+ arr = new nsCOMArray<nsXULTemplateResultRDF>();
+ mMemoryElementToResultMap.Put(hash, arr);
+ }
+
+ // results may be added more than once so they will all get deleted properly
+ arr->AppendObject(aResult);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTemplateQueryProcessorRDF::RemoveMemoryElements(const Instantiation& aInst,
+ nsXULTemplateResultRDF* aResult)
+{
+ // Remove the results mapped by the supporting MemoryElement
+ MemoryElementSet::ConstIterator last = aInst.mSupport.Last();
+ for (MemoryElementSet::ConstIterator element = aInst.mSupport.First();
+ element != last; ++element) {
+
+ PLHashNumber hash = (element.operator->())->Hash();
+
+ nsCOMArray<nsXULTemplateResultRDF>* arr;
+ if (mMemoryElementToResultMap.Get(hash, &arr)) {
+ int32_t index = arr->IndexOf(aResult);
+ if (index >= 0)
+ arr->RemoveObjectAt(index);
+
+ uint32_t length = arr->Count();
+ if (! length)
+ mMemoryElementToResultMap.Remove(hash);
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsXULTemplateQueryProcessorRDF::RetractElement(const MemoryElement& aMemoryElement)
+{
+ if (! mBuilder)
+ return;
+
+ // when an assertion is removed, look through the memory elements and
+ // find results that are associated with them. Those results will need
+ // to be removed because they no longer match.
+ PLHashNumber hash = aMemoryElement.Hash();
+
+ nsCOMArray<nsXULTemplateResultRDF>* arr;
+ if (mMemoryElementToResultMap.Get(hash, &arr)) {
+ uint32_t length = arr->Count();
+
+ for (int32_t r = length - 1; r >= 0; r--) {
+ nsXULTemplateResultRDF* result = (*arr)[r];
+ if (result) {
+ // because the memory elements are hashed by an integer,
+ // sometimes two different memory elements will have the same
+ // hash code. In this case we check the result to make sure
+ // and only remove those that refer to that memory element.
+ if (result->HasMemoryElement(aMemoryElement)) {
+ nsITemplateRDFQuery* query = result->Query();
+ if (query) {
+ nsCOMPtr<nsIDOMNode> querynode;
+ query->GetQueryNode(getter_AddRefs(querynode));
+
+ mBuilder->RemoveResult(result);
+ }
+
+ // a call to RemoveMemoryElements may have removed it
+ if (!mMemoryElementToResultMap.Get(hash, nullptr))
+ return;
+
+ // the array should have been reduced by one, but check
+ // just to make sure
+ uint32_t newlength = arr->Count();
+ if (r > (int32_t)newlength)
+ r = newlength;
+ }
+ }
+ }
+
+ // if there are no items left, remove the memory element from the hashtable
+ if (!arr->Count())
+ mMemoryElementToResultMap.Remove(hash);
+ }
+}
+
+int32_t
+nsXULTemplateQueryProcessorRDF::GetContainerIndexOf(nsIXULTemplateResult* aResult)
+{
+ // get the reference variable and look up the container in the result
+ nsCOMPtr<nsISupports> ref;
+ nsresult rv = aResult->GetBindingObjectFor(mRefVariable,
+ getter_AddRefs(ref));
+ if (NS_FAILED(rv) || !mDB)
+ return -1;
+
+ nsCOMPtr<nsIRDFResource> container = do_QueryInterface(ref);
+ if (container) {
+ // if the container is an RDF Seq, return the index of the result
+ // in the container.
+ bool isSequence = false;
+ gRDFContainerUtils->IsSeq(mDB, container, &isSequence);
+ if (isSequence) {
+ nsCOMPtr<nsIRDFResource> resource;
+ aResult->GetResource(getter_AddRefs(resource));
+ if (resource) {
+ int32_t index;
+ gRDFContainerUtils->IndexOf(mDB, container, resource, &index);
+ return index;
+ }
+ }
+ }
+
+ // if the container isn't a Seq, or the result isn't in the container,
+ // return -1 indicating no index.
+ return -1;
+}
+
+nsresult
+nsXULTemplateQueryProcessorRDF::GetSortValue(nsIXULTemplateResult* aResult,
+ nsIRDFResource* aPredicate,
+ nsIRDFResource* aSortPredicate,
+ nsISupports** aResultNode)
+{
+ nsCOMPtr<nsIRDFResource> source;
+ nsresult rv = aResult->GetResource(getter_AddRefs(source));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIRDFNode> value;
+ if (source && mDB) {
+ // first check predicate?sort=true so that datasources may use a
+ // custom value for sorting
+ rv = mDB->GetTarget(source, aSortPredicate, true,
+ getter_AddRefs(value));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!value) {
+ rv = mDB->GetTarget(source, aPredicate, true,
+ getter_AddRefs(value));
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ }
+
+ *aResultNode = value;
+ NS_IF_ADDREF(*aResultNode);
+ return NS_OK;
+}
diff --git a/dom/xul/templates/nsXULTemplateQueryProcessorRDF.h b/dom/xul/templates/nsXULTemplateQueryProcessorRDF.h
new file mode 100644
index 000000000..30ac34d23
--- /dev/null
+++ b/dom/xul/templates/nsXULTemplateQueryProcessorRDF.h
@@ -0,0 +1,349 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#ifndef nsXULTemplateQueryProcessorRDF_h__
+#define nsXULTemplateQueryProcessorRDF_h__
+
+#include "nsIRDFContainer.h"
+#include "nsIRDFContainerUtils.h"
+#include "nsIRDFDataSource.h"
+#include "nsIRDFObserver.h"
+#include "nsIRDFService.h"
+#include "nsIXULTemplateBuilder.h"
+#include "nsIXULTemplateQueryProcessor.h"
+#include "nsCollationCID.h"
+
+#include "nsResourceSet.h"
+#include "nsRuleNetwork.h"
+#include "nsRDFQuery.h"
+#include "nsRDFBinding.h"
+#include "nsXULTemplateResultSetRDF.h"
+#include "nsCOMArray.h"
+#include "nsString.h"
+#include "nsClassHashtable.h"
+#include "nsRefPtrHashtable.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+
+#include "mozilla/Logging.h"
+extern mozilla::LazyLogModule gXULTemplateLog;
+
+class nsIContent;
+class nsXULTemplateResultRDF;
+
+/**
+ * An object that generates results from a query on an RDF graph
+ */
+class nsXULTemplateQueryProcessorRDF final : public nsIXULTemplateQueryProcessor,
+ public nsIRDFObserver
+{
+public:
+ typedef nsTArray<RefPtr<nsXULTemplateResultRDF> > ResultArray;
+
+ nsXULTemplateQueryProcessorRDF();
+
+ nsresult InitGlobals();
+
+ // nsISupports interface
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsXULTemplateQueryProcessorRDF,
+ nsIXULTemplateQueryProcessor)
+
+ // nsIXULTemplateQueryProcessor interface
+ NS_DECL_NSIXULTEMPLATEQUERYPROCESSOR
+
+ // nsIRDFObserver interface
+ NS_DECL_NSIRDFOBSERVER
+
+ /*
+ * Propagate all changes through the rule network when an assertion is
+ * added to the graph, adding any new results.
+ */
+ nsresult
+ Propagate(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget);
+
+ /*
+ * Retract all changes through the rule network when an assertion is
+ * removed from the graph, removing any results that no longer match.
+ */
+ nsresult
+ Retract(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget);
+
+ /*
+ * Synchronize results when the graph changes, updating their bindings.
+ */
+ nsresult
+ SynchronizeAll(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aOldTarget,
+ nsIRDFNode* aNewTarget);
+
+ /*
+ * Return true if a resource is a container
+ */
+ nsresult
+ CheckContainer(nsIRDFResource* aTargetResource,
+ bool* aIsContainer);
+
+ /*
+ * Check if a resource does not have any children
+ */
+ nsresult
+ CheckEmpty(nsIRDFResource* aTargetResource,
+ bool* aIsEmpty);
+
+ /**
+ * Check if a resource is a separator
+ */
+ nsresult
+ CheckIsSeparator(nsIRDFResource* aResource, bool* aIsSeparator);
+
+ /*
+ * Compute the containment properties which are additional arcs which
+ * indicate that a node is a container, in additional to the RDF container
+ * tests. The computed list is stored in mContainmentProperties
+ */
+ nsresult
+ ComputeContainmentProperties(nsIDOMNode* aRootNode);
+
+ /**
+ * Compile a query that uses the extended template syntax. The last
+ * compiled node of the query is returned as aLastNode. This node will
+ * have been added to mAllTests which owns the node.
+ */
+ nsresult
+ CompileExtendedQuery(nsRDFQuery* aQuery,
+ nsIContent* aConditions,
+ TestNode** aLastNode);
+
+ /**
+ * Compile a single query child and return the compiled node in aResult.
+ * This node will have been added to mAllTests which owns the node and
+ * set as a child of aParentNode.
+ */
+ virtual nsresult
+ CompileQueryChild(nsIAtom* aTag,
+ nsRDFQuery* aQuery,
+ nsIContent* aConditions,
+ TestNode* aParentNode,
+ TestNode** aResult);
+
+ /**
+ * Parse the value of a property test assertion for a condition or a simple
+ * rule based on the parseType attribute into the appropriate literal type.
+ */
+ nsresult ParseLiteral(const nsString& aParseType,
+ const nsString& aValue,
+ nsIRDFNode** aResult);
+
+ /**
+ * Compile a <triple> condition and return the compiled node in aResult.
+ * This node will have been added to mAllTests which owns the node and
+ * set as a child of aParentNode.
+ */
+ nsresult
+ CompileTripleCondition(nsRDFQuery* aQuery,
+ nsIContent* aCondition,
+ TestNode* aParentNode,
+ TestNode** aResult);
+
+ /**
+ * Compile a <member> condition and return the compiled node in aResult.
+ * This node will have been added to mAllTests which owns the node and
+ * set as a child of aParentNode.
+ */
+ nsresult
+ CompileMemberCondition(nsRDFQuery* aQuery,
+ nsIContent* aCondition,
+ TestNode* aParentNode,
+ TestNode** aResult);
+
+ /**
+ * Add the default rules shared by all simple queries. This creates
+ * the content start node followed by a member test. The member TestNode
+ * is returned in aChildNode. Both nodes will have been added to mAllTests
+ * which owns the nodes.
+ */
+ nsresult
+ AddDefaultSimpleRules(nsRDFQuery* aQuery,
+ TestNode** aChildNode);
+
+ /**
+ * Compile a query that's specified using the simple template
+ * syntax. Each TestNode is created in a chain, the last compiled node
+ * is returned as aLastNode. All nodes will have been added to mAllTests
+ * which owns the nodes.
+ */
+ nsresult
+ CompileSimpleQuery(nsRDFQuery* aQuery,
+ nsIContent* aQueryElement,
+ TestNode** aLastNode);
+
+ RDFBindingSet*
+ GetBindingsForRule(nsIDOMNode* aRule);
+
+ /*
+ * Indicate that a result is dependant on a particular resource. When an
+ * assertion is added to or removed from the graph involving that
+ * resource, that result must be recalculated.
+ */
+ void
+ AddBindingDependency(nsXULTemplateResultRDF* aResult,
+ nsIRDFResource* aResource);
+
+ /**
+ * Remove a dependency a result has on a particular resource.
+ */
+ void
+ RemoveBindingDependency(nsXULTemplateResultRDF* aResult,
+ nsIRDFResource* aResource);
+
+ /**
+ * A memory element is a hash of an RDF triple. One exists for each triple
+ * that was involved in generating a result. This function adds this to a
+ * map, keyed by memory element, when the value is a list of results that
+ * depend on that memory element. When an RDF triple is removed from the
+ * datasource, RetractElement is called, and this map is examined to
+ * determine which results are no longer valid.
+ */
+ nsresult
+ AddMemoryElements(const Instantiation& aInst,
+ nsXULTemplateResultRDF* aResult);
+
+ /**
+ * Remove the memory elements associated with a result when the result is
+ * no longer being used.
+ */
+ nsresult
+ RemoveMemoryElements(const Instantiation& aInst,
+ nsXULTemplateResultRDF* aResult);
+
+ /**
+ * Remove the results associated with a memory element since the
+ * RDF triple the memory element is a hash of has been removed.
+ */
+ void RetractElement(const MemoryElement& aMemoryElement);
+
+ /**
+ * Return the index of a result's resource in its RDF container
+ */
+ int32_t
+ GetContainerIndexOf(nsIXULTemplateResult* aResult);
+
+ /**
+ * Given a result and a predicate to sort on, get the target value of
+ * the triple to use for sorting. The sort predicate is the predicate
+ * with '?sort=true' appended.
+ */
+ nsresult
+ GetSortValue(nsIXULTemplateResult* aResult,
+ nsIRDFResource* aPredicate,
+ nsIRDFResource* aSortPredicate,
+ nsISupports** aResultNode);
+
+ nsIRDFDataSource* GetDataSource() { return mDB; }
+
+ nsIXULTemplateBuilder* GetBuilder() { return mBuilder; }
+
+ nsResourceSet& ContainmentProperties() { return mContainmentProperties; }
+
+ nsresult
+ Log(const char* aOperation,
+ nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget);
+
+#define LOG(_op, _src, _prop, _targ) \
+ Log(_op, _src, _prop, _targ)
+
+protected:
+ ~nsXULTemplateQueryProcessorRDF();
+
+ // We are an observer of the composite datasource. The cycle is
+ // broken when the document is destroyed.
+ nsCOMPtr<nsIRDFDataSource> mDB;
+
+ // weak reference to the builder, cleared when the document is destroyed
+ nsIXULTemplateBuilder* mBuilder;
+
+ // true if the query processor has been initialized
+ bool mQueryProcessorRDFInited;
+
+ // true if results have been generated. Once set, bindings can no longer
+ // be added. If they were, the binding value arrays for results that have
+ // already been generated would be the wrong size
+ bool mGenerationStarted;
+
+ // nesting level for RDF batch notifications
+ int32_t mUpdateBatchNest;
+
+ // containment properties that are checked to determine if a resource is
+ // a container
+ nsResourceSet mContainmentProperties;
+
+ // the end node of the default simple node hierarchy
+ TestNode* mSimpleRuleMemberTest;
+
+ // the reference variable
+ nsCOMPtr<nsIAtom> mRefVariable;
+
+ // the last ref that was calculated, used for simple rules
+ nsCOMPtr<nsIXULTemplateResult> mLastRef;
+
+ /**
+ * A map between nsIRDFNodes that form the left-hand side (the subject) of
+ * a <binding> and an array of nsIXULTemplateResults. When a new assertion
+ * is added to the graph involving a particular rdf node, it is looked up
+ * in this binding map. If it exists, the corresponding results must then
+ * be synchronized.
+ */
+ nsClassHashtable<nsISupportsHashKey, ResultArray> mBindingDependencies;
+
+ /**
+ * A map between memory elements and an array of nsIXULTemplateResults.
+ * When a triple is unasserted from the graph, the corresponding results
+ * no longer match so they must be removed.
+ */
+ nsClassHashtable<nsUint32HashKey,
+ nsCOMArray<nsXULTemplateResultRDF> > mMemoryElementToResultMap;
+
+ // map of the rules to the bindings for those rules.
+ // XXXndeakin this might be better just as an array since there is usually
+ // ten or fewer rules
+ nsRefPtrHashtable<nsISupportsHashKey, RDFBindingSet> mRuleToBindingsMap;
+
+ /**
+ * The queries
+ */
+ nsTArray<nsCOMPtr<nsITemplateRDFQuery> > mQueries;
+
+ /**
+ * All of the RDF tests in the rule network, which are checked when a new
+ * assertion is added to the graph. This is a subset of mAllTests, which
+ * also includes non-RDF tests.
+ */
+ ReteNodeSet mRDFTests;
+
+ /**
+ * All of the tests in the rule network, owned by this list
+ */
+ ReteNodeSet mAllTests;
+
+ // pseudo-constants
+ static nsrefcnt gRefCnt;
+
+public:
+ static nsIRDFService* gRDFService;
+ static nsIRDFContainerUtils* gRDFContainerUtils;
+ static nsIRDFResource* kNC_BookmarkSeparator;
+ static nsIRDFResource* kRDF_type;
+};
+
+#endif // nsXULTemplateQueryProcessorRDF_h__
diff --git a/dom/xul/templates/nsXULTemplateQueryProcessorStorage.cpp b/dom/xul/templates/nsXULTemplateQueryProcessorStorage.cpp
new file mode 100644
index 000000000..a70307bf5
--- /dev/null
+++ b/dom/xul/templates/nsXULTemplateQueryProcessorStorage.cpp
@@ -0,0 +1,502 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "prprf.h"
+
+#include "nsIDOMNodeList.h"
+#include "nsUnicharUtils.h"
+
+#include "nsArrayUtils.h"
+#include "nsVariant.h"
+#include "nsAppDirectoryServiceDefs.h"
+
+#include "nsIURI.h"
+#include "nsIFileChannel.h"
+#include "nsIFile.h"
+#include "nsGkAtoms.h"
+#include "nsContentUtils.h"
+
+#include "nsXULTemplateBuilder.h"
+#include "nsXULTemplateResultStorage.h"
+#include "nsXULContentUtils.h"
+#include "nsXULSortService.h"
+
+#include "mozIStorageService.h"
+#include "nsIChannel.h"
+#include "nsIDocument.h"
+#include "nsNetUtil.h"
+
+//----------------------------------------------------------------------
+//
+// nsXULTemplateResultSetStorage
+//
+
+NS_IMPL_ISUPPORTS(nsXULTemplateResultSetStorage, nsISimpleEnumerator)
+
+
+nsXULTemplateResultSetStorage::nsXULTemplateResultSetStorage(mozIStorageStatement* aStatement)
+ : mStatement(aStatement)
+{
+ uint32_t count;
+ nsresult rv = aStatement->GetColumnCount(&count);
+ if (NS_FAILED(rv)) {
+ mStatement = nullptr;
+ return;
+ }
+ for (uint32_t c = 0; c < count; c++) {
+ nsAutoCString name;
+ rv = aStatement->GetColumnName(c, name);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIAtom> columnName = NS_Atomize(NS_LITERAL_CSTRING("?") + name);
+ mColumnNames.AppendObject(columnName);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultSetStorage::HasMoreElements(bool *aResult)
+{
+ if (!mStatement) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ nsresult rv = mStatement->ExecuteStep(aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Because the nsXULTemplateResultSetStorage is owned by many nsXULTemplateResultStorage objects,
+ // it could live longer than it needed to get results.
+ // So we destroy the statement to free resources when all results are fetched
+ if (!*aResult) {
+ mStatement = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultSetStorage::GetNext(nsISupports **aResult)
+{
+ nsXULTemplateResultStorage* result =
+ new nsXULTemplateResultStorage(this);
+ *aResult = result;
+ NS_ADDREF(result);
+ return NS_OK;
+}
+
+
+int32_t
+nsXULTemplateResultSetStorage::GetColumnIndex(nsIAtom* aColumnName)
+{
+ int32_t count = mColumnNames.Count();
+ for (int32_t c = 0; c < count; c++) {
+ if (mColumnNames[c] == aColumnName)
+ return c;
+ }
+
+ return -1;
+}
+
+void
+nsXULTemplateResultSetStorage::FillColumnValues(nsCOMArray<nsIVariant>& aArray)
+{
+ if (!mStatement)
+ return;
+
+ int32_t count = mColumnNames.Count();
+
+ for (int32_t c = 0; c < count; c++) {
+ RefPtr<nsVariant> value = new nsVariant();
+
+ int32_t type;
+ mStatement->GetTypeOfIndex(c, &type);
+
+ if (type == mStatement->VALUE_TYPE_INTEGER) {
+ int64_t val = mStatement->AsInt64(c);
+ value->SetAsInt64(val);
+ }
+ else if (type == mStatement->VALUE_TYPE_FLOAT) {
+ double val = mStatement->AsDouble(c);
+ value->SetAsDouble(val);
+ }
+ else {
+ nsAutoString val;
+ nsresult rv = mStatement->GetString(c, val);
+ if (NS_FAILED(rv))
+ value->SetAsAString(EmptyString());
+ else
+ value->SetAsAString(val);
+ }
+ aArray.AppendObject(value);
+ }
+}
+
+
+
+//----------------------------------------------------------------------
+//
+// nsXULTemplateQueryProcessorStorage
+//
+
+NS_IMPL_ISUPPORTS(nsXULTemplateQueryProcessorStorage,
+ nsIXULTemplateQueryProcessor)
+
+
+nsXULTemplateQueryProcessorStorage::nsXULTemplateQueryProcessorStorage()
+ : mGenerationStarted(false)
+{
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorStorage::GetDatasource(nsIArray* aDataSources,
+ nsIDOMNode* aRootNode,
+ bool aIsTrusted,
+ nsIXULTemplateBuilder* aBuilder,
+ bool* aShouldDelayBuilding,
+ nsISupports** aReturn)
+{
+ *aReturn = nullptr;
+ *aShouldDelayBuilding = false;
+
+ if (!aIsTrusted) {
+ return NS_OK;
+ }
+
+ uint32_t length;
+ nsresult rv = aDataSources->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (length == 0) {
+ return NS_OK;
+ }
+
+ // We get only the first uri. This query processor supports
+ // only one database at a time.
+ nsCOMPtr<nsIURI> uri;
+ uri = do_QueryElementAt(aDataSources, 0);
+
+ if (!uri) {
+ // No uri in the list of datasources
+ return NS_OK;
+ }
+
+ nsCOMPtr<mozIStorageService> storage =
+ do_GetService("@mozilla.org/storage/service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> databaseFile;
+ nsAutoCString scheme;
+ rv = uri->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (scheme.EqualsLiteral("profile")) {
+
+ nsAutoCString path;
+ rv = uri->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (path.IsEmpty()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(databaseFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = databaseFile->AppendNative(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsINode> node = do_QueryInterface(aRootNode);
+
+ // The following channel is never openend, so it does not matter what
+ // securityFlags we pass; let's follow the principle of least privilege.
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ node,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(channel, &rv);
+ if (NS_FAILED(rv)) { // if it fails, not a file url
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_STORAGE_BAD_URI);
+ return rv;
+ }
+
+ rv = fileChannel->GetFile(getter_AddRefs(databaseFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // ok now we have an URI of a sqlite file
+ nsCOMPtr<mozIStorageConnection> connection;
+ rv = storage->OpenDatabase(databaseFile, getter_AddRefs(connection));
+ if (NS_FAILED(rv)) {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_STORAGE_CANNOT_OPEN_DATABASE);
+ return rv;
+ }
+
+ connection.forget(aReturn);
+ return NS_OK;
+}
+
+
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorStorage::InitializeForBuilding(nsISupports* aDatasource,
+ nsIXULTemplateBuilder* aBuilder,
+ nsIDOMNode* aRootNode)
+{
+ NS_ENSURE_STATE(!mGenerationStarted);
+
+ mStorageConnection = do_QueryInterface(aDatasource);
+ if (!mStorageConnection)
+ return NS_ERROR_INVALID_ARG;
+
+ bool ready;
+ mStorageConnection->GetConnectionReady(&ready);
+ if (!ready)
+ return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorStorage::Done()
+{
+ mGenerationStarted = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorStorage::CompileQuery(nsIXULTemplateBuilder* aBuilder,
+ nsIDOMNode* aQueryNode,
+ nsIAtom* aRefVariable,
+ nsIAtom* aMemberVariable,
+ nsISupports** aReturn)
+{
+ nsCOMPtr<nsIDOMNodeList> childNodes;
+ aQueryNode->GetChildNodes(getter_AddRefs(childNodes));
+
+ uint32_t length;
+ childNodes->GetLength(&length);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsCOMPtr<nsIContent> queryContent = do_QueryInterface(aQueryNode);
+ nsAutoString sqlQuery;
+
+ // Let's get all text nodes (which should be the query)
+ if (!nsContentUtils::GetNodeTextContent(queryContent, false, sqlQuery, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsresult rv = mStorageConnection->CreateStatement(NS_ConvertUTF16toUTF8(sqlQuery),
+ getter_AddRefs(statement));
+ if (NS_FAILED(rv)) {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_STORAGE_BAD_QUERY);
+ return rv;
+ }
+
+ uint32_t parameterCount = 0;
+ for (nsIContent* child = queryContent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+
+ if (child->NodeInfo()->Equals(nsGkAtoms::param, kNameSpaceID_XUL)) {
+ nsAutoString value;
+ if (!nsContentUtils::GetNodeTextContent(child, false, value, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint32_t index = parameterCount;
+ nsAutoString name, indexValue;
+
+ if (child->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name)) {
+ rv = statement->GetParameterIndex(NS_ConvertUTF16toUTF8(name),
+ &index);
+ if (NS_FAILED(rv)) {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_STORAGE_UNKNOWN_QUERY_PARAMETER);
+ return rv;
+ }
+ parameterCount++;
+ }
+ else if (child->GetAttr(kNameSpaceID_None, nsGkAtoms::index, indexValue)) {
+ PR_sscanf(NS_ConvertUTF16toUTF8(indexValue).get(),"%d",&index);
+ if (index > 0)
+ index--;
+ }
+ else {
+ parameterCount++;
+ }
+
+ static nsIContent::AttrValuesArray sTypeValues[] =
+ { &nsGkAtoms::int32, &nsGkAtoms::integer, &nsGkAtoms::int64,
+ &nsGkAtoms::null, &nsGkAtoms::double_, &nsGkAtoms::string, nullptr };
+
+ int32_t typeError = 1;
+ int32_t typeValue = child->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type,
+ sTypeValues, eCaseMatters);
+ rv = NS_ERROR_ILLEGAL_VALUE;
+ int32_t valInt32 = 0;
+ int64_t valInt64 = 0;
+ double valFloat = 0;
+
+ switch (typeValue) {
+ case 0:
+ case 1:
+ typeError = PR_sscanf(NS_ConvertUTF16toUTF8(value).get(),"%d",&valInt32);
+ if (typeError > 0)
+ rv = statement->BindInt32ByIndex(index, valInt32);
+ break;
+ case 2:
+ typeError = PR_sscanf(NS_ConvertUTF16toUTF8(value).get(),"%lld",&valInt64);
+ if (typeError > 0)
+ rv = statement->BindInt64ByIndex(index, valInt64);
+ break;
+ case 3:
+ rv = statement->BindNullByIndex(index);
+ break;
+ case 4:
+ typeError = PR_sscanf(NS_ConvertUTF16toUTF8(value).get(),"%lf",&valFloat);
+ if (typeError > 0)
+ rv = statement->BindDoubleByIndex(index, valFloat);
+ break;
+ case 5:
+ case nsIContent::ATTR_MISSING:
+ rv = statement->BindStringByIndex(index, value);
+ break;
+ default:
+ typeError = 0;
+ }
+
+ if (typeError <= 0) {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_STORAGE_WRONG_TYPE_QUERY_PARAMETER);
+ return rv;
+ }
+
+ if (NS_FAILED(rv)) {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_STORAGE_QUERY_PARAMETER_NOT_BOUND);
+ return rv;
+ }
+ }
+ }
+
+ *aReturn = statement;
+ NS_IF_ADDREF(*aReturn);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorStorage::GenerateResults(nsISupports* aDatasource,
+ nsIXULTemplateResult* aRef,
+ nsISupports* aQuery,
+ nsISimpleEnumerator** aResults)
+{
+ mGenerationStarted = true;
+
+ nsCOMPtr<mozIStorageStatement> statement = do_QueryInterface(aQuery);
+ if (!statement)
+ return NS_ERROR_FAILURE;
+
+ nsXULTemplateResultSetStorage* results =
+ new nsXULTemplateResultSetStorage(statement);
+ *aResults = results;
+ NS_ADDREF(*aResults);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorStorage::AddBinding(nsIDOMNode* aRuleNode,
+ nsIAtom* aVar,
+ nsIAtom* aRef,
+ const nsAString& aExpr)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorStorage::TranslateRef(nsISupports* aDatasource,
+ const nsAString& aRefString,
+ nsIXULTemplateResult** aRef)
+{
+ nsXULTemplateResultStorage* result =
+ new nsXULTemplateResultStorage(nullptr);
+ *aRef = result;
+ NS_ADDREF(*aRef);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorStorage::CompareResults(nsIXULTemplateResult* aLeft,
+ nsIXULTemplateResult* aRight,
+ nsIAtom* aVar,
+ uint32_t aSortHints,
+ int32_t* aResult)
+{
+ *aResult = 0;
+ if (!aVar)
+ return NS_OK;
+
+ // We're going to see if values are integers or float, to perform
+ // a suitable comparison
+ nsCOMPtr<nsISupports> leftValue, rightValue;
+ if (aLeft)
+ aLeft->GetBindingObjectFor(aVar, getter_AddRefs(leftValue));
+ if (aRight)
+ aRight->GetBindingObjectFor(aVar, getter_AddRefs(rightValue));
+
+ if (leftValue && rightValue) {
+ nsCOMPtr<nsIVariant> vLeftValue = do_QueryInterface(leftValue);
+ nsCOMPtr<nsIVariant> vRightValue = do_QueryInterface(rightValue);
+
+ if (vLeftValue && vRightValue) {
+ nsresult rv1, rv2;
+ uint16_t vtypeL, vtypeR;
+ vLeftValue->GetDataType(&vtypeL);
+ vRightValue->GetDataType(&vtypeR);
+
+ if (vtypeL == vtypeR) {
+ if (vtypeL == nsIDataType::VTYPE_INT64) {
+ int64_t leftValue, rightValue;
+ rv1 = vLeftValue->GetAsInt64(&leftValue);
+ rv2 = vRightValue->GetAsInt64(&rightValue);
+ if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2)) {
+ if (leftValue > rightValue)
+ *aResult = 1;
+ else if (leftValue < rightValue)
+ *aResult = -1;
+ return NS_OK;
+ }
+ }
+ else if (vtypeL == nsIDataType::VTYPE_DOUBLE) {
+ double leftValue, rightValue;
+ rv1 = vLeftValue->GetAsDouble(&leftValue);
+ rv2 = vRightValue->GetAsDouble(&rightValue);
+ if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2)) {
+ if (leftValue > rightValue)
+ *aResult = 1;
+ else if (leftValue < rightValue)
+ *aResult = -1;
+ return NS_OK;
+ }
+ }
+ }
+ }
+ }
+
+ // Values are not integers or floats, so we just compare them as simple strings
+ nsAutoString leftVal;
+ if (aLeft)
+ aLeft->GetBindingFor(aVar, leftVal);
+
+ nsAutoString rightVal;
+ if (aRight)
+ aRight->GetBindingFor(aVar, rightVal);
+
+ *aResult = XULSortServiceImpl::CompareValues(leftVal, rightVal, aSortHints);
+ return NS_OK;
+}
diff --git a/dom/xul/templates/nsXULTemplateQueryProcessorStorage.h b/dom/xul/templates/nsXULTemplateQueryProcessorStorage.h
new file mode 100644
index 000000000..8c52f139b
--- /dev/null
+++ b/dom/xul/templates/nsXULTemplateQueryProcessorStorage.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXULTemplateQueryProcessorStorage_h__
+#define nsXULTemplateQueryProcessorStorage_h__
+
+#include "nsIXULTemplateBuilder.h"
+#include "nsIXULTemplateQueryProcessor.h"
+
+#include "nsISimpleEnumerator.h"
+#include "nsCOMArray.h"
+#include "nsIVariant.h"
+
+#include "mozIStorageValueArray.h"
+#include "mozIStorageStatement.h"
+#include "mozIStorageConnection.h"
+#include "mozilla/Attributes.h"
+
+class nsXULTemplateQueryProcessorStorage;
+
+class nsXULTemplateResultSetStorage final : public nsISimpleEnumerator
+{
+private:
+
+ nsCOMPtr<mozIStorageStatement> mStatement;
+
+ nsCOMArray<nsIAtom> mColumnNames;
+
+ ~nsXULTemplateResultSetStorage() {}
+
+public:
+
+ // nsISupports interface
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator interface
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ explicit nsXULTemplateResultSetStorage(mozIStorageStatement* aStatement);
+
+ int32_t GetColumnIndex(nsIAtom* aColumnName);
+
+ void FillColumnValues(nsCOMArray<nsIVariant>& aArray);
+
+};
+
+class nsXULTemplateQueryProcessorStorage final : public nsIXULTemplateQueryProcessor
+{
+public:
+
+ nsXULTemplateQueryProcessorStorage();
+
+ // nsISupports interface
+ NS_DECL_ISUPPORTS
+
+ // nsIXULTemplateQueryProcessor interface
+ NS_DECL_NSIXULTEMPLATEQUERYPROCESSOR
+
+private:
+
+ ~nsXULTemplateQueryProcessorStorage() {}
+
+ nsCOMPtr<mozIStorageConnection> mStorageConnection;
+ bool mGenerationStarted;
+};
+
+#endif // nsXULTemplateQueryProcessorStorage_h__
diff --git a/dom/xul/templates/nsXULTemplateQueryProcessorXML.cpp b/dom/xul/templates/nsXULTemplateQueryProcessorXML.cpp
new file mode 100644
index 000000000..1c6fed252
--- /dev/null
+++ b/dom/xul/templates/nsXULTemplateQueryProcessorXML.cpp
@@ -0,0 +1,449 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMEvent.h"
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsComponentManagerUtils.h"
+#include "nsGkAtoms.h"
+#include "nsIURI.h"
+#include "nsIArray.h"
+#include "nsIScriptContext.h"
+#include "nsArrayUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsXULContentUtils.h"
+#include "mozilla/dom/XPathEvaluator.h"
+#include "nsXULTemplateQueryProcessorXML.h"
+#include "nsXULTemplateResultXML.h"
+#include "nsXULSortService.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/XMLHttpRequest.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(nsXMLQuery, nsXMLQuery)
+
+//----------------------------------------------------------------------
+//
+// nsXULTemplateResultSetXML
+//
+
+NS_IMPL_ISUPPORTS(nsXULTemplateResultSetXML, nsISimpleEnumerator)
+
+NS_IMETHODIMP
+nsXULTemplateResultSetXML::HasMoreElements(bool *aResult)
+{
+ // if GetSnapshotLength failed, then the return type was not a set of
+ // nodes, so just return false in this case.
+ ErrorResult rv;
+ uint32_t length = mResults->GetSnapshotLength(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ *aResult = false;
+ return NS_OK;
+ }
+
+ *aResult = mPosition < length;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultSetXML::GetNext(nsISupports **aResult)
+{
+ ErrorResult rv;
+ nsINode* node = mResults->SnapshotItem(mPosition, rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+
+ nsXULTemplateResultXML* result =
+ new nsXULTemplateResultXML(mQuery, node->AsContent(), mBindingSet);
+
+ ++mPosition;
+ *aResult = result;
+ NS_ADDREF(result);
+ return NS_OK;
+}
+
+
+//----------------------------------------------------------------------
+//
+// nsXULTemplateQueryProcessorXML
+//
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULTemplateQueryProcessorXML)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULTemplateQueryProcessorXML)
+ tmp->mRuleToBindingsMap.Clear();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoot)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mEvaluator)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateBuilder)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRequest)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULTemplateQueryProcessorXML)
+ for (auto it = tmp->mRuleToBindingsMap.Iter(); !it.Done(); it.Next()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mRuleToBindingsMap key");
+ cb.NoteXPCOMChild(it.Key());
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvaluator)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateBuilder)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRequest)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULTemplateQueryProcessorXML)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULTemplateQueryProcessorXML)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULTemplateQueryProcessorXML)
+ NS_INTERFACE_MAP_ENTRY(nsIXULTemplateQueryProcessor)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXULTemplateQueryProcessor)
+NS_INTERFACE_MAP_END
+
+/*
+ * Only the first datasource in aDataSource is used, which should be either an
+ * nsIURI of an XML document, or a DOM node. If the former, GetDatasource will
+ * load the document asynchronously and return null in aResult. Once the
+ * document has loaded, the builder's datasource will be set to the XML
+ * document. If the datasource is a DOM node, the node will be returned in
+ * aResult.
+ */
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorXML::GetDatasource(nsIArray* aDataSources,
+ nsIDOMNode* aRootNode,
+ bool aIsTrusted,
+ nsIXULTemplateBuilder* aBuilder,
+ bool* aShouldDelayBuilding,
+ nsISupports** aResult)
+{
+ *aResult = nullptr;
+ *aShouldDelayBuilding = false;
+
+ nsresult rv;
+ uint32_t length;
+
+ aDataSources->GetLength(&length);
+ if (length == 0)
+ return NS_OK;
+
+ // we get only the first item, because the query processor supports only
+ // one document as a datasource
+
+ nsCOMPtr<nsIDOMNode> node = do_QueryElementAt(aDataSources, 0);
+ if (node) {
+ return CallQueryInterface(node, aResult);
+ }
+
+ nsCOMPtr<nsIURI> uri = do_QueryElementAt(aDataSources, 0);
+ if (!uri)
+ return NS_ERROR_UNEXPECTED;
+
+ nsAutoCString uriStr;
+ rv = uri->GetSpec(uriStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIContent> root = do_QueryInterface(aRootNode);
+ if (!root)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIDocument> doc = root->GetUncomposedDoc();
+ if (!doc)
+ return NS_ERROR_UNEXPECTED;
+
+ nsIPrincipal *docPrincipal = doc->NodePrincipal();
+
+ bool hasHadScriptObject = true;
+ nsIScriptGlobalObject* scriptObject =
+ doc->GetScriptHandlingObject(hasHadScriptObject);
+ NS_ENSURE_STATE(scriptObject);
+
+ nsCOMPtr<nsIXMLHttpRequest> req =
+ do_CreateInstance(NS_XMLHTTPREQUEST_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = req->Init(docPrincipal, scriptObject, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = req->Open(NS_LITERAL_CSTRING("GET"), uriStr, true,
+ EmptyString(), EmptyString());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<EventTarget> target(do_QueryInterface(req));
+ rv = target->AddEventListener(NS_LITERAL_STRING("load"), this, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = target->AddEventListener(NS_LITERAL_STRING("error"), this, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = req->Send(nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mTemplateBuilder = aBuilder;
+ mRequest = req;
+
+ *aShouldDelayBuilding = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorXML::InitializeForBuilding(nsISupports* aDatasource,
+ nsIXULTemplateBuilder* aBuilder,
+ nsIDOMNode* aRootNode)
+{
+ if (mGenerationStarted)
+ return NS_ERROR_UNEXPECTED;
+
+ // the datasource is either a document or a DOM element
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDatasource);
+ if (doc)
+ mRoot = doc->GetDocumentElement();
+ else
+ mRoot = do_QueryInterface(aDatasource);
+ NS_ENSURE_STATE(mRoot);
+
+ mEvaluator = new XPathEvaluator();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorXML::Done()
+{
+ mGenerationStarted = false;
+
+ mRuleToBindingsMap.Clear();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorXML::CompileQuery(nsIXULTemplateBuilder* aBuilder,
+ nsIDOMNode* aQueryNode,
+ nsIAtom* aRefVariable,
+ nsIAtom* aMemberVariable,
+ nsISupports** _retval)
+{
+ *_retval = nullptr;
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aQueryNode);
+
+ nsAutoString expr;
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::expr, expr);
+
+ // if an expression is not specified, then the default is to
+ // just take all of the children
+ if (expr.IsEmpty())
+ expr.Assign('*');
+
+ ErrorResult rv;
+ nsAutoPtr<XPathExpression> compiledexpr;
+ compiledexpr = CreateExpression(expr, content, rv);
+ if (rv.Failed()) {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BAD_XPATH);
+ return rv.StealNSResult();
+ }
+
+ RefPtr<nsXMLQuery> query =
+ new nsXMLQuery(this, aMemberVariable, Move(compiledexpr));
+
+ for (nsIContent* condition = content->GetFirstChild();
+ condition;
+ condition = condition->GetNextSibling()) {
+
+ if (condition->NodeInfo()->Equals(nsGkAtoms::assign,
+ kNameSpaceID_XUL)) {
+ nsAutoString var;
+ condition->GetAttr(kNameSpaceID_None, nsGkAtoms::var, var);
+
+ nsAutoString expr;
+ condition->GetAttr(kNameSpaceID_None, nsGkAtoms::expr, expr);
+
+ // ignore assignments without a variable or an expression
+ if (!var.IsEmpty() && !expr.IsEmpty()) {
+ compiledexpr = CreateExpression(expr, condition, rv);
+ if (rv.Failed()) {
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BAD_ASSIGN_XPATH);
+ return rv.StealNSResult();
+ }
+
+ nsCOMPtr<nsIAtom> varatom = NS_Atomize(var);
+
+ query->AddBinding(varatom, Move(compiledexpr));
+ }
+ }
+ }
+
+ query.forget(_retval);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorXML::GenerateResults(nsISupports* aDatasource,
+ nsIXULTemplateResult* aRef,
+ nsISupports* aQuery,
+ nsISimpleEnumerator** aResults)
+{
+ if (!aQuery)
+ return NS_ERROR_INVALID_ARG;
+
+ mGenerationStarted = true;
+
+ nsCOMPtr<nsXMLQuery> xmlquery = do_QueryInterface(aQuery);
+ if (!xmlquery)
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsINode> context;
+ if (aRef)
+ aRef->GetBindingObjectFor(xmlquery->GetMemberVariable(),
+ getter_AddRefs(supports));
+ context = do_QueryInterface(supports);
+ if (!context)
+ context = mRoot;
+
+ XPathExpression* expr = xmlquery->GetResultsExpression();
+ if (!expr)
+ return NS_ERROR_FAILURE;
+
+ ErrorResult rv;
+ RefPtr<XPathResult> exprresults =
+ expr->Evaluate(*context, XPathResult::ORDERED_NODE_SNAPSHOT_TYPE,
+ nullptr, rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+
+ RefPtr<nsXULTemplateResultSetXML> results =
+ new nsXULTemplateResultSetXML(xmlquery, exprresults.forget(),
+ xmlquery->GetBindingSet());
+
+ results.forget(aResults);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorXML::AddBinding(nsIDOMNode* aRuleNode,
+ nsIAtom* aVar,
+ nsIAtom* aRef,
+ const nsAString& aExpr)
+{
+ if (mGenerationStarted)
+ return NS_ERROR_FAILURE;
+
+ RefPtr<nsXMLBindingSet> bindings = mRuleToBindingsMap.GetWeak(aRuleNode);
+ if (!bindings) {
+ bindings = new nsXMLBindingSet();
+ mRuleToBindingsMap.Put(aRuleNode, bindings);
+ }
+
+ nsCOMPtr<nsINode> ruleNode = do_QueryInterface(aRuleNode);
+
+ ErrorResult rv;
+ nsAutoPtr<XPathExpression> compiledexpr;
+ compiledexpr = CreateExpression(aExpr, ruleNode, rv);
+ if (rv.Failed()) {
+ rv.SuppressException();
+ nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_BAD_BINDING_XPATH);
+ return NS_OK;
+ }
+
+ // aRef isn't currently used for XML query processors
+ bindings->AddBinding(aVar, Move(compiledexpr));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorXML::TranslateRef(nsISupports* aDatasource,
+ const nsAString& aRefString,
+ nsIXULTemplateResult** aRef)
+{
+ *aRef = nullptr;
+
+ // the datasource is either a document or a DOM element
+ nsCOMPtr<Element> rootElement;
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDatasource);
+ if (doc)
+ rootElement = doc->GetRootElement();
+ else
+ rootElement = do_QueryInterface(aDatasource);
+
+ // if no root element, just return. The document may not have loaded yet
+ if (!rootElement)
+ return NS_OK;
+
+ RefPtr<nsXULTemplateResultXML> result = new nsXULTemplateResultXML(nullptr, rootElement, nullptr);
+ result.forget(aRef);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorXML::CompareResults(nsIXULTemplateResult* aLeft,
+ nsIXULTemplateResult* aRight,
+ nsIAtom* aVar,
+ uint32_t aSortHints,
+ int32_t* aResult)
+{
+ *aResult = 0;
+ if (!aVar)
+ return NS_OK;
+
+ nsAutoString leftVal;
+ if (aLeft)
+ aLeft->GetBindingFor(aVar, leftVal);
+
+ nsAutoString rightVal;
+ if (aRight)
+ aRight->GetBindingFor(aVar, rightVal);
+
+ *aResult = XULSortServiceImpl::CompareValues(leftVal, rightVal, aSortHints);
+ return NS_OK;
+}
+
+nsXMLBindingSet*
+nsXULTemplateQueryProcessorXML::GetOptionalBindingsForRule(nsIDOMNode* aRuleNode)
+{
+ return mRuleToBindingsMap.GetWeak(aRuleNode);
+}
+
+XPathExpression*
+nsXULTemplateQueryProcessorXML::CreateExpression(const nsAString& aExpr,
+ nsINode* aNode,
+ ErrorResult& aRv)
+{
+ return mEvaluator->CreateExpression(aExpr, aNode, aRv);
+}
+
+NS_IMETHODIMP
+nsXULTemplateQueryProcessorXML::HandleEvent(nsIDOMEvent* aEvent)
+{
+ NS_PRECONDITION(aEvent, "aEvent null");
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+
+ if (eventType.EqualsLiteral("load") && mTemplateBuilder) {
+ NS_ASSERTION(mRequest, "request was not set");
+ nsCOMPtr<nsIDOMDocument> doc;
+ if (NS_SUCCEEDED(mRequest->GetResponseXML(getter_AddRefs(doc))))
+ mTemplateBuilder->SetDatasource(doc);
+
+ // to avoid leak. we don't need it after...
+ mTemplateBuilder = nullptr;
+ mRequest = nullptr;
+ }
+ else if (eventType.EqualsLiteral("error")) {
+ mTemplateBuilder = nullptr;
+ mRequest = nullptr;
+ }
+
+ return NS_OK;
+}
diff --git a/dom/xul/templates/nsXULTemplateQueryProcessorXML.h b/dom/xul/templates/nsXULTemplateQueryProcessorXML.h
new file mode 100644
index 000000000..dec508415
--- /dev/null
+++ b/dom/xul/templates/nsXULTemplateQueryProcessorXML.h
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXULTemplateQueryProcessorXML_h__
+#define nsXULTemplateQueryProcessorXML_h__
+
+#include "nsIXULTemplateBuilder.h"
+#include "nsIXULTemplateQueryProcessor.h"
+
+#include "nsAutoPtr.h"
+#include "nsISimpleEnumerator.h"
+#include "nsString.h"
+#include "nsCOMArray.h"
+#include "nsRefPtrHashtable.h"
+#include "nsIDOMEventListener.h"
+#include "nsIDOMXPathEvaluator.h"
+#include "nsXMLBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIXMLHttpRequest.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/XPathEvaluator.h"
+#include "mozilla/dom/XPathResult.h"
+
+class nsXULTemplateQueryProcessorXML;
+
+#define NS_IXMLQUERY_IID \
+ {0x0358d692, 0xccce, 0x4a97, \
+ { 0xb2, 0x51, 0xba, 0x8f, 0x17, 0x0f, 0x3b, 0x6f }}
+
+class nsXMLQuery final : public nsISupports
+{
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IXMLQUERY_IID)
+
+ NS_DECL_ISUPPORTS
+
+ // return a weak reference to the processor the query was created from
+ nsXULTemplateQueryProcessorXML* Processor() { return mProcessor; }
+
+ // return a weak reference t the member variable for the query
+ nsIAtom* GetMemberVariable() { return mMemberVariable; }
+
+ // return a weak reference to the expression used to generate results
+ mozilla::dom::XPathExpression* GetResultsExpression()
+ { return mResultsExpr; }
+
+ // return a weak reference to the additional required bindings
+ nsXMLBindingSet* GetBindingSet() { return mRequiredBindings; }
+
+ // add a required binding for the query
+ void
+ AddBinding(nsIAtom* aVar, nsAutoPtr<mozilla::dom::XPathExpression>&& aExpr)
+ {
+ if (!mRequiredBindings) {
+ mRequiredBindings = new nsXMLBindingSet();
+ }
+
+ mRequiredBindings->AddBinding(aVar, mozilla::Move(aExpr));
+ }
+
+ nsXMLQuery(nsXULTemplateQueryProcessorXML* aProcessor,
+ nsIAtom* aMemberVariable,
+ nsAutoPtr<mozilla::dom::XPathExpression>&& aResultsExpr)
+ : mProcessor(aProcessor),
+ mMemberVariable(aMemberVariable),
+ mResultsExpr(aResultsExpr)
+ { }
+
+ protected:
+ ~nsXMLQuery() {}
+
+ nsXULTemplateQueryProcessorXML* mProcessor;
+
+ nsCOMPtr<nsIAtom> mMemberVariable;
+
+ nsAutoPtr<mozilla::dom::XPathExpression> mResultsExpr;
+
+ RefPtr<nsXMLBindingSet> mRequiredBindings;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsXMLQuery, NS_IXMLQUERY_IID)
+
+class nsXULTemplateResultSetXML final : public nsISimpleEnumerator
+{
+private:
+
+ // reference back to the query
+ nsCOMPtr<nsXMLQuery> mQuery;
+
+ // the binding set created from <assign> nodes
+ RefPtr<nsXMLBindingSet> mBindingSet;
+
+ // set of results contained in this enumerator
+ RefPtr<mozilla::dom::XPathResult> mResults;
+
+ // current position within the list of results
+ uint32_t mPosition;
+
+ ~nsXULTemplateResultSetXML() {}
+
+public:
+
+ // nsISupports interface
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator interface
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ nsXULTemplateResultSetXML(nsXMLQuery* aQuery,
+ already_AddRefed<mozilla::dom::XPathResult> aResults,
+ nsXMLBindingSet* aBindingSet)
+ : mQuery(aQuery),
+ mBindingSet(aBindingSet),
+ mResults(aResults),
+ mPosition(0)
+ {}
+};
+
+class nsXULTemplateQueryProcessorXML final : public nsIXULTemplateQueryProcessor,
+ public nsIDOMEventListener
+{
+public:
+
+ nsXULTemplateQueryProcessorXML()
+ : mGenerationStarted(false)
+ {}
+
+ // nsISupports interface
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsXULTemplateQueryProcessorXML,
+ nsIXULTemplateQueryProcessor)
+
+ // nsIXULTemplateQueryProcessor interface
+ NS_DECL_NSIXULTEMPLATEQUERYPROCESSOR
+
+ // nsIDOMEventListener interface
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ nsXMLBindingSet*
+ GetOptionalBindingsForRule(nsIDOMNode* aRuleNode);
+
+ // create an XPath expression from aExpr, using aNode for
+ // resolving namespaces
+ mozilla::dom::XPathExpression*
+ CreateExpression(const nsAString& aExpr,
+ nsINode* aNode,
+ mozilla::ErrorResult& aRv);
+
+private:
+
+ ~nsXULTemplateQueryProcessorXML() {}
+
+ bool mGenerationStarted;
+
+ nsRefPtrHashtable<nsISupportsHashKey, nsXMLBindingSet> mRuleToBindingsMap;
+
+ nsCOMPtr<mozilla::dom::Element> mRoot;
+
+ RefPtr<mozilla::dom::XPathEvaluator> mEvaluator;
+
+ nsCOMPtr<nsIXULTemplateBuilder> mTemplateBuilder;
+
+ nsCOMPtr<nsIXMLHttpRequest> mRequest;
+};
+
+
+#endif // nsXULTemplateQueryProcessorXML_h__
diff --git a/dom/xul/templates/nsXULTemplateResultRDF.cpp b/dom/xul/templates/nsXULTemplateResultRDF.cpp
new file mode 100644
index 000000000..9d53bab7d
--- /dev/null
+++ b/dom/xul/templates/nsXULTemplateResultRDF.cpp
@@ -0,0 +1,208 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsXULTemplateResultRDF.h"
+#include "nsXULContentUtils.h"
+
+// XXXndeakin for some reason, making this class have classinfo breaks trees.
+//#include "nsIDOMClassInfo.h"
+
+NS_IMPL_CYCLE_COLLECTION(nsXULTemplateResultRDF, mQuery)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULTemplateResultRDF)
+ NS_INTERFACE_MAP_ENTRY(nsIXULTemplateResult)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULTemplateResultRDF)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULTemplateResultRDF)
+
+nsXULTemplateResultRDF::nsXULTemplateResultRDF(nsIRDFResource* aNode)
+ : mQuery(nullptr),
+ mNode(aNode)
+{
+}
+
+nsXULTemplateResultRDF::nsXULTemplateResultRDF(nsRDFQuery* aQuery,
+ const Instantiation& aInst,
+ nsIRDFResource *aNode)
+ : mQuery(aQuery),
+ mNode(aNode),
+ mInst(aInst)
+{
+}
+
+nsXULTemplateResultRDF::~nsXULTemplateResultRDF()
+{
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultRDF::GetIsContainer(bool* aIsContainer)
+{
+ *aIsContainer = false;
+
+ if (mNode) {
+ nsXULTemplateQueryProcessorRDF* processor = GetProcessor();
+ if (processor)
+ return processor->CheckContainer(mNode, aIsContainer);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultRDF::GetIsEmpty(bool* aIsEmpty)
+{
+ *aIsEmpty = true;
+
+ if (mNode) {
+ nsXULTemplateQueryProcessorRDF* processor = GetProcessor();
+ if (processor)
+ return processor->CheckEmpty(mNode, aIsEmpty);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultRDF::GetMayProcessChildren(bool* aMayProcessChildren)
+{
+ // RDF always allows recursion
+ *aMayProcessChildren = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultRDF::GetId(nsAString& aId)
+{
+ if (! mNode)
+ return NS_ERROR_FAILURE;
+
+ const char* uri;
+ mNode->GetValueConst(&uri);
+
+ CopyUTF8toUTF16(uri, aId);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultRDF::GetResource(nsIRDFResource** aResource)
+{
+ *aResource = mNode;
+ NS_IF_ADDREF(*aResource);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultRDF::GetType(nsAString& aType)
+{
+ aType.Truncate();
+
+ nsresult rv = NS_OK;
+
+ nsXULTemplateQueryProcessorRDF* processor = GetProcessor();
+ if (processor) {
+ bool found;
+ rv = processor->CheckIsSeparator(mNode, &found);
+ if (NS_SUCCEEDED(rv) && found)
+ aType.AssignLiteral("separator");
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultRDF::GetBindingFor(nsIAtom* aVar, nsAString& aValue)
+{
+ nsCOMPtr<nsIRDFNode> val;
+ GetAssignment(aVar, getter_AddRefs(val));
+
+ return nsXULContentUtils::GetTextForNode(val, aValue);
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultRDF::GetBindingObjectFor(nsIAtom* aVar, nsISupports** aValue)
+{
+ GetAssignment(aVar, (nsIRDFNode **)aValue);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultRDF::RuleMatched(nsISupports* aQuery, nsIDOMNode* aRuleNode)
+{
+ // when a rule matches, set the bindings that must be used.
+ nsXULTemplateQueryProcessorRDF* processor = GetProcessor();
+ if (processor) {
+ RDFBindingSet* bindings = processor->GetBindingsForRule(aRuleNode);
+ if (bindings) {
+ nsresult rv = mBindingValues.SetBindingSet(bindings);
+ if (NS_FAILED(rv))
+ return rv;
+
+ bindings->AddDependencies(mNode, this);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultRDF::HasBeenRemoved()
+{
+ // when a result is no longer used, clean up the dependencies and
+ // memory elements that refer to it
+ mBindingValues.RemoveDependencies(mNode, this);
+
+ nsXULTemplateQueryProcessorRDF* processor = GetProcessor();
+ if (processor)
+ processor->RemoveMemoryElements(mInst, this);
+
+ return NS_OK;
+}
+
+
+void
+nsXULTemplateResultRDF::GetAssignment(nsIAtom* aVar, nsIRDFNode** aValue)
+{
+ // look up a variable in the assignments map
+ *aValue = nullptr;
+ mInst.mAssignments.GetAssignmentFor(aVar, aValue);
+
+ // if not found, look up the variable in the bindings
+ if (! *aValue)
+ mBindingValues.GetAssignmentFor(this, aVar, aValue);
+}
+
+
+bool
+nsXULTemplateResultRDF::SyncAssignments(nsIRDFResource* aSubject,
+ nsIRDFResource* aPredicate,
+ nsIRDFNode* aTarget)
+{
+ // synchronize the bindings when an assertion is added or removed
+ RDFBindingSet* bindingset = mBindingValues.GetBindingSet();
+ if (bindingset) {
+ return bindingset->SyncAssignments(aSubject, aPredicate, aTarget,
+ (aSubject == mNode) ? mQuery->GetMemberVariable() : nullptr,
+ this, mBindingValues);
+ }
+
+ return false;
+}
+
+bool
+nsXULTemplateResultRDF::HasMemoryElement(const MemoryElement& aMemoryElement)
+{
+ MemoryElementSet::ConstIterator last = mInst.mSupport.Last();
+ for (MemoryElementSet::ConstIterator element = mInst.mSupport.First();
+ element != last; ++element) {
+ if ((*element).Equals(aMemoryElement))
+ return true;
+ }
+
+ return false;
+}
diff --git a/dom/xul/templates/nsXULTemplateResultRDF.h b/dom/xul/templates/nsXULTemplateResultRDF.h
new file mode 100644
index 000000000..cb5022420
--- /dev/null
+++ b/dom/xul/templates/nsXULTemplateResultRDF.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXULTemplateResultRDF_h__
+#define nsXULTemplateResultRDF_h__
+
+#include "nsCOMPtr.h"
+#include "nsIRDFResource.h"
+#include "nsXULTemplateQueryProcessorRDF.h"
+#include "nsRDFQuery.h"
+#include "nsRuleNetwork.h"
+#include "nsIXULTemplateResult.h"
+#include "nsRDFBinding.h"
+#include "mozilla/Attributes.h"
+
+/**
+ * A single result of a query on an RDF graph
+ */
+class nsXULTemplateResultRDF final : public nsIXULTemplateResult
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsXULTemplateResultRDF)
+
+ NS_DECL_NSIXULTEMPLATERESULT
+
+ explicit nsXULTemplateResultRDF(nsIRDFResource* aNode);
+
+ nsXULTemplateResultRDF(nsRDFQuery* aQuery,
+ const Instantiation& aInst,
+ nsIRDFResource* aNode);
+
+ nsITemplateRDFQuery* Query() { return mQuery; }
+
+ nsXULTemplateQueryProcessorRDF* GetProcessor()
+ {
+ return (mQuery ? mQuery->Processor() : nullptr);
+ }
+
+ /**
+ * Get the value of a variable, first by looking in the assignments and
+ * then the bindings
+ */
+ void
+ GetAssignment(nsIAtom* aVar, nsIRDFNode** aValue);
+
+ /**
+ * Synchronize the bindings after a change in the RDF graph. Bindings that
+ * would be affected will be assigned appropriately based on the change.
+ */
+ bool
+ SyncAssignments(nsIRDFResource* aSubject,
+ nsIRDFResource* aPredicate,
+ nsIRDFNode* aTarget);
+
+ /**
+ * Return true if the result has an instantiation involving a particular
+ * memory element.
+ */
+ bool
+ HasMemoryElement(const MemoryElement& aMemoryElement);
+
+protected:
+ ~nsXULTemplateResultRDF();
+
+ // query that generated the result
+ nsCOMPtr<nsITemplateRDFQuery> mQuery;
+
+ // resource node
+ nsCOMPtr<nsIRDFResource> mNode;
+
+ // data computed from query
+ Instantiation mInst;
+
+ // extra assignments made by rules (<binding> tags)
+ nsBindingValues mBindingValues;
+};
+
+#endif // nsXULTemplateResultRDF_h__
diff --git a/dom/xul/templates/nsXULTemplateResultSetRDF.cpp b/dom/xul/templates/nsXULTemplateResultSetRDF.cpp
new file mode 100644
index 000000000..1ab9a7959
--- /dev/null
+++ b/dom/xul/templates/nsXULTemplateResultSetRDF.cpp
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsXULTemplateResultSetRDF.h"
+#include "nsXULTemplateQueryProcessorRDF.h"
+
+NS_IMPL_ISUPPORTS(nsXULTemplateResultSetRDF, nsISimpleEnumerator)
+
+NS_IMETHODIMP
+nsXULTemplateResultSetRDF::HasMoreElements(bool *aResult)
+{
+ *aResult = true;
+
+ nsCOMPtr<nsIRDFNode> node;
+
+ if (! mInstantiations || ! mQuery) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ if (mCheckedNext) {
+ if (!mCurrent || mCurrent == &(mInstantiations->mHead))
+ *aResult = false;
+ return NS_OK;
+ }
+
+ mCheckedNext = true;
+
+ do {
+ if (mCurrent) {
+ mCurrent = mCurrent->mNext;
+ if (mCurrent == &(mInstantiations->mHead)) {
+ *aResult = false;
+ return NS_OK;
+ }
+ }
+ else {
+ *aResult = ! mInstantiations->Empty();
+ if (*aResult)
+ mCurrent = mInstantiations->mHead.mNext;
+ }
+
+ // get the value of the member variable. If it is not set, skip
+ // the result and move on to the next result
+ if (mCurrent) {
+ mCurrent->mInstantiation.mAssignments.
+ GetAssignmentFor(mQuery->mMemberVariable, getter_AddRefs(node));
+ }
+
+ // only resources may be used as results
+ mResource = do_QueryInterface(node);
+ } while (! mResource);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultSetRDF::GetNext(nsISupports **aResult)
+{
+ if (!aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ if (!mCurrent || !mCheckedNext)
+ return NS_ERROR_FAILURE;
+
+ RefPtr<nsXULTemplateResultRDF> nextresult =
+ new nsXULTemplateResultRDF(mQuery, mCurrent->mInstantiation, mResource);
+ if (!nextresult)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // add the supporting memory elements to the processor's map. These are
+ // used to remove the results when an assertion is removed from the graph
+ mProcessor->AddMemoryElements(mCurrent->mInstantiation, nextresult);
+
+ mCheckedNext = false;
+
+ nextresult.forget(aResult);
+
+ return NS_OK;
+}
diff --git a/dom/xul/templates/nsXULTemplateResultSetRDF.h b/dom/xul/templates/nsXULTemplateResultSetRDF.h
new file mode 100644
index 000000000..76aa28774
--- /dev/null
+++ b/dom/xul/templates/nsXULTemplateResultSetRDF.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXULTemplateResultSetRDF_h__
+#define nsXULTemplateResultSetRDF_h__
+
+#include "nsISimpleEnumerator.h"
+#include "nsRuleNetwork.h"
+#include "nsRDFQuery.h"
+#include "nsXULTemplateResultRDF.h"
+#include "mozilla/Attributes.h"
+
+class nsXULTemplateQueryProcessorRDF;
+class nsXULTemplateResultRDF;
+
+/**
+ * An enumerator used to iterate over a set of results.
+ */
+class nsXULTemplateResultSetRDF final : public nsISimpleEnumerator
+{
+private:
+ nsXULTemplateQueryProcessorRDF* mProcessor;
+
+ nsRDFQuery* mQuery;
+
+ const InstantiationSet* mInstantiations;
+
+ nsCOMPtr<nsIRDFResource> mResource;
+
+ InstantiationSet::List *mCurrent;
+
+ bool mCheckedNext;
+
+ ~nsXULTemplateResultSetRDF()
+ {
+ delete mInstantiations;
+ }
+
+public:
+
+ // nsISupports interface
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator interface
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ nsXULTemplateResultSetRDF(nsXULTemplateQueryProcessorRDF *aProcessor,
+ nsRDFQuery* aQuery,
+ const InstantiationSet* aInstantiations)
+ : mProcessor(aProcessor),
+ mQuery(aQuery),
+ mInstantiations(aInstantiations),
+ mCurrent(nullptr),
+ mCheckedNext(false)
+ { }
+};
+
+#endif // nsXULTemplateResultSetRDF_h__
diff --git a/dom/xul/templates/nsXULTemplateResultStorage.cpp b/dom/xul/templates/nsXULTemplateResultStorage.cpp
new file mode 100644
index 000000000..b840fc2c7
--- /dev/null
+++ b/dom/xul/templates/nsXULTemplateResultStorage.cpp
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIServiceManager.h"
+#include "nsRDFCID.h"
+#include "nsIRDFService.h"
+#include "nsString.h"
+#include "nsXULTemplateResultStorage.h"
+
+NS_IMPL_ISUPPORTS(nsXULTemplateResultStorage, nsIXULTemplateResult)
+
+nsXULTemplateResultStorage::nsXULTemplateResultStorage(nsXULTemplateResultSetStorage* aResultSet)
+{
+ static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+ nsCOMPtr<nsIRDFService> rdfService = do_GetService(kRDFServiceCID);
+ rdfService->GetAnonymousResource(getter_AddRefs(mNode));
+ mResultSet = aResultSet;
+ if (aResultSet) {
+ mResultSet->FillColumnValues(mValues);
+ }
+}
+
+nsXULTemplateResultStorage::~nsXULTemplateResultStorage()
+{
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultStorage::GetIsContainer(bool* aIsContainer)
+{
+ *aIsContainer = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultStorage::GetIsEmpty(bool* aIsEmpty)
+{
+ *aIsEmpty = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultStorage::GetMayProcessChildren(bool* aMayProcessChildren)
+{
+ *aMayProcessChildren = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultStorage::GetId(nsAString& aId)
+{
+ const char* uri = nullptr;
+ mNode->GetValueConst(&uri);
+
+ aId.Assign(NS_ConvertUTF8toUTF16(uri));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultStorage::GetResource(nsIRDFResource** aResource)
+{
+ *aResource = mNode;
+ NS_IF_ADDREF(*aResource);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultStorage::GetType(nsAString& aType)
+{
+ aType.Truncate();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsXULTemplateResultStorage::GetBindingFor(nsIAtom* aVar, nsAString& aValue)
+{
+ NS_ENSURE_ARG_POINTER(aVar);
+
+ aValue.Truncate();
+ if (!mResultSet) {
+ return NS_OK;
+ }
+
+ int32_t idx = mResultSet->GetColumnIndex(aVar);
+ if (idx < 0) {
+ return NS_OK;
+ }
+
+ nsIVariant * value = mValues[idx];
+ if (value) {
+ value->GetAsAString(aValue);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultStorage::GetBindingObjectFor(nsIAtom* aVar, nsISupports** aValue)
+{
+ NS_ENSURE_ARG_POINTER(aVar);
+
+ if (mResultSet) {
+ int32_t idx = mResultSet->GetColumnIndex(aVar);
+ if (idx >= 0) {
+ *aValue = mValues[idx];
+ NS_IF_ADDREF(*aValue);
+ return NS_OK;
+ }
+ }
+ *aValue = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultStorage::RuleMatched(nsISupports* aQuery, nsIDOMNode* aRuleNode)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultStorage::HasBeenRemoved()
+{
+ return NS_OK;
+}
diff --git a/dom/xul/templates/nsXULTemplateResultStorage.h b/dom/xul/templates/nsXULTemplateResultStorage.h
new file mode 100644
index 000000000..75cf7b05a
--- /dev/null
+++ b/dom/xul/templates/nsXULTemplateResultStorage.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXULTemplateResultStorage_h__
+#define nsXULTemplateResultStorage_h__
+
+#include "nsXULTemplateQueryProcessorStorage.h"
+#include "nsIRDFResource.h"
+#include "nsIXULTemplateResult.h"
+#include "mozilla/Attributes.h"
+
+/**
+ * A single result of a query from mozstorage
+ */
+class nsXULTemplateResultStorage final : public nsIXULTemplateResult
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIXULTEMPLATERESULT
+
+ explicit nsXULTemplateResultStorage(nsXULTemplateResultSetStorage* aResultSet);
+
+protected:
+
+ ~nsXULTemplateResultStorage();
+
+ RefPtr<nsXULTemplateResultSetStorage> mResultSet;
+
+ nsCOMArray<nsIVariant> mValues;
+
+ nsCOMPtr<nsIRDFResource> mNode;
+};
+
+#endif // nsXULTemplateResultStorage_h__
diff --git a/dom/xul/templates/nsXULTemplateResultXML.cpp b/dom/xul/templates/nsXULTemplateResultXML.cpp
new file mode 100644
index 000000000..6ac4e6004
--- /dev/null
+++ b/dom/xul/templates/nsXULTemplateResultXML.cpp
@@ -0,0 +1,189 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIServiceManager.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMElement.h"
+#include "nsIContent.h"
+
+#include "nsIRDFService.h"
+
+#include "nsXULTemplateResultXML.h"
+#include "nsXMLBinding.h"
+
+static uint32_t sTemplateId = 0;
+
+NS_IMPL_ISUPPORTS(nsXULTemplateResultXML, nsIXULTemplateResult)
+
+nsXULTemplateResultXML::nsXULTemplateResultXML(nsXMLQuery* aQuery,
+ nsIContent* aNode,
+ nsXMLBindingSet* aBindings)
+ : mQuery(aQuery), mNode(aNode)
+{
+ // If the node has an id, create the uri from it. Otherwise, there isn't
+ // anything to identify the node with so just use a somewhat random number.
+ nsCOMPtr<nsIAtom> id = mNode->GetID();
+ if (id) {
+ nsCOMPtr<nsIURI> uri = mNode->GetBaseURI();
+ nsAutoCString spec;
+ uri->GetSpec(spec);
+
+ mId = NS_ConvertUTF8toUTF16(spec);
+
+ nsAutoString idstr;
+ id->ToString(idstr);
+ mId += NS_LITERAL_STRING("#") + idstr;
+ }
+ else {
+ nsAutoString rowid(NS_LITERAL_STRING("row"));
+ rowid.AppendInt(++sTemplateId);
+ mId.Assign(rowid);
+ }
+
+ if (aBindings)
+ mRequiredValues.SetBindingSet(aBindings);
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultXML::GetIsContainer(bool* aIsContainer)
+{
+ // a node is considered a container if it has children
+ *aIsContainer = mNode && mNode->HasChildNodes();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultXML::GetIsEmpty(bool* aIsEmpty)
+{
+ // a node is considered empty if it has no elements as children
+ nsCOMPtr<nsIContent> content = do_QueryInterface(mNode);
+ if (content) {
+ for (nsIContent* child = content->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child->IsElement()) {
+ *aIsEmpty = false;
+ return NS_OK;
+ }
+ }
+ }
+
+ *aIsEmpty = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultXML::GetMayProcessChildren(bool* aMayProcessChildren)
+{
+ *aMayProcessChildren = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultXML::GetId(nsAString& aId)
+{
+ aId = mId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultXML::GetResource(nsIRDFResource** aResource)
+{
+ *aResource = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultXML::GetType(nsAString& aType)
+{
+ aType.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultXML::GetBindingFor(nsIAtom* aVar, nsAString& aValue)
+{
+ NS_ENSURE_ARG_POINTER(aVar);
+
+ // get the position of the atom in the variables table
+ nsXMLBinding* binding;
+
+ int32_t idx = mRequiredValues.LookupTargetIndex(aVar, &binding);
+ if (idx >= 0) {
+ mRequiredValues.GetStringAssignmentFor(this, binding, idx, aValue);
+ return NS_OK;
+ }
+
+ idx = mOptionalValues.LookupTargetIndex(aVar, &binding);
+ if (idx >= 0) {
+ mOptionalValues.GetStringAssignmentFor(this, binding, idx, aValue);
+ return NS_OK;
+ }
+
+ // if the variable is not bound, just use the variable name as the name of
+ // an attribute to retrieve
+ nsAutoString attr;
+ aVar->ToString(attr);
+
+ if (attr.Length() > 1) {
+ nsCOMPtr<nsIDOMElement> element = do_QueryInterface(mNode);
+ if (element)
+ return element->GetAttribute(Substring(attr, 1), aValue);
+ }
+
+ aValue.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultXML::GetBindingObjectFor(nsIAtom* aVar, nsISupports** aValue)
+{
+ NS_ENSURE_ARG_POINTER(aVar);
+
+ nsXMLBinding* binding;
+ nsCOMPtr<nsISupports> node;
+
+ if (mQuery && aVar == mQuery->GetMemberVariable()) {
+ node = mNode;
+ }
+ else {
+ int32_t idx = mRequiredValues.LookupTargetIndex(aVar, &binding);
+ if (idx > 0) {
+ node = mRequiredValues.GetNodeAssignmentFor(this, binding, idx);
+ }
+ else {
+ idx = mOptionalValues.LookupTargetIndex(aVar, &binding);
+ if (idx > 0) {
+ node = mOptionalValues.GetNodeAssignmentFor(this, binding, idx);
+ }
+ }
+ }
+
+ node.forget(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultXML::RuleMatched(nsISupports* aQueryNode,
+ nsIDOMNode* aRuleNode)
+{
+ // when a rule matches, set the bindings that must be used.
+ nsXULTemplateQueryProcessorXML* processor = mQuery ? mQuery->Processor() :
+ nullptr;
+ if (processor) {
+ nsXMLBindingSet* bindings =
+ processor->GetOptionalBindingsForRule(aRuleNode);
+ if (bindings)
+ mOptionalValues.SetBindingSet(bindings);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTemplateResultXML::HasBeenRemoved()
+{
+ return NS_OK;
+}
diff --git a/dom/xul/templates/nsXULTemplateResultXML.h b/dom/xul/templates/nsXULTemplateResultXML.h
new file mode 100644
index 000000000..3c42c0829
--- /dev/null
+++ b/dom/xul/templates/nsXULTemplateResultXML.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXULTemplateResultXML_h__
+#define nsXULTemplateResultXML_h__
+
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+#include "nsIURI.h"
+#include "nsIRDFResource.h"
+#include "nsXULTemplateQueryProcessorXML.h"
+#include "nsIXULTemplateResult.h"
+#include "mozilla/Attributes.h"
+
+/**
+ * An single result of an query
+ */
+class nsXULTemplateResultXML final : public nsIXULTemplateResult
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIXULTEMPLATERESULT
+
+ nsXULTemplateResultXML(nsXMLQuery* aQuery,
+ nsIContent* aNode,
+ nsXMLBindingSet* aBindings);
+
+ nsIContent* Node()
+ {
+ return mNode;
+ }
+
+protected:
+
+ ~nsXULTemplateResultXML() {}
+
+ // ID used for persisting data. It is constructed using the mNode's
+ // base uri plus the node's id to form 'baseuri#id'. If the node has no
+ // id, then an id of the form 'row<some number>' is generated. In the
+ // latter case, persistence will not work as there won't be a unique id.
+ nsAutoString mId;
+
+ // query that generated the result
+ nsCOMPtr<nsXMLQuery> mQuery;
+
+ // context node in datasource
+ nsCOMPtr<nsIContent> mNode;
+
+ // assignments in query
+ nsXMLBindingValues mRequiredValues;
+
+ // extra assignments made by rules (<binding> tags)
+ nsXMLBindingValues mOptionalValues;
+};
+
+#endif // nsXULTemplateResultXML_h__
diff --git a/dom/xul/templates/nsXULTreeBuilder.cpp b/dom/xul/templates/nsXULTreeBuilder.cpp
new file mode 100644
index 000000000..b42133484
--- /dev/null
+++ b/dom/xul/templates/nsXULTreeBuilder.cpp
@@ -0,0 +1,1881 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nscore.h"
+#include "nsError.h"
+#include "nsIContent.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsIDOMElement.h"
+#include "nsIBoxObject.h"
+#include "nsITreeBoxObject.h"
+#include "nsITreeSelection.h"
+#include "nsITreeColumns.h"
+#include "nsITreeView.h"
+#include "nsTreeUtils.h"
+#include "nsIServiceManager.h"
+#include "nsReadableUtils.h"
+#include "nsQuickSort.h"
+#include "nsTreeRows.h"
+#include "nsTemplateRule.h"
+#include "nsTemplateMatch.h"
+#include "nsGkAtoms.h"
+#include "nsXULContentUtils.h"
+#include "nsXULTemplateBuilder.h"
+#include "nsIXULSortService.h"
+#include "nsTArray.h"
+#include "nsUnicharUtils.h"
+#include "nsNameSpaceManager.h"
+#include "nsDOMClassInfoID.h"
+#include "nsWhitespaceTokenizer.h"
+#include "nsTreeContentView.h"
+#include "nsIXULStore.h"
+#include "mozilla/BinarySearch.h"
+
+// For security check
+#include "nsIDocument.h"
+
+/**
+ * A XUL template builder that serves as an tree view, allowing
+ * (pretty much) arbitrary RDF to be presented in an tree.
+ */
+class nsXULTreeBuilder : public nsXULTemplateBuilder,
+ public nsIXULTreeBuilder,
+ public nsINativeTreeView
+{
+public:
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsXULTreeBuilder, nsXULTemplateBuilder)
+
+ // nsIXULTreeBuilder
+ NS_DECL_NSIXULTREEBUILDER
+
+ // nsITreeView
+ NS_DECL_NSITREEVIEW
+ // nsINativeTreeView: Untrusted code can use us
+ NS_IMETHOD EnsureNative() override { return NS_OK; }
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+protected:
+ friend nsresult
+ NS_NewXULTreeBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+ friend struct ResultComparator;
+
+ nsXULTreeBuilder();
+ ~nsXULTreeBuilder();
+
+ /**
+ * Uninitialize the template builder
+ */
+ virtual void Uninit(bool aIsFinal) override;
+
+ /**
+ * Get sort variables from the active <treecol>
+ */
+ nsresult
+ EnsureSortVariables();
+
+ virtual nsresult
+ RebuildAll() override;
+
+ /**
+ * Given a row, use the row's match to figure out the appropriate
+ * <treerow> in the rule's <action>.
+ */
+ nsresult
+ GetTemplateActionRowFor(int32_t aRow, nsIContent** aResult);
+
+ /**
+ * Given a row and a column ID, use the row's match to figure out
+ * the appropriate <treecell> in the rule's <action>.
+ */
+ nsresult
+ GetTemplateActionCellFor(int32_t aRow, nsITreeColumn* aCol, nsIContent** aResult);
+
+ /**
+ * Return the resource corresponding to a row in the tree.
+ */
+ nsresult
+ GetResourceFor(int32_t aRow, nsIRDFResource** aResource);
+
+ /**
+ * Open a container row, inserting the container's children into
+ * the view.
+ */
+ nsresult
+ OpenContainer(int32_t aIndex, nsIXULTemplateResult* aResult);
+
+ /**
+ * Helper for OpenContainer, recursively open subtrees, remembering
+ * persisted ``open'' state
+ */
+ nsresult
+ OpenSubtreeOf(nsTreeRows::Subtree* aSubtree,
+ int32_t aIndex,
+ nsIXULTemplateResult *aResult,
+ int32_t* aDelta);
+
+ nsresult
+ OpenSubtreeForQuerySet(nsTreeRows::Subtree* aSubtree,
+ int32_t aIndex,
+ nsIXULTemplateResult *aResult,
+ nsTemplateQuerySet* aQuerySet,
+ int32_t* aDelta,
+ nsTArray<int32_t>& open);
+
+ /**
+ * Close a container row, removing the container's childrem from
+ * the view.
+ */
+ nsresult
+ CloseContainer(int32_t aIndex);
+
+ /**
+ * Remove the matches for the rows in a subtree
+ */
+ nsresult
+ RemoveMatchesFor(nsTreeRows::Subtree& subtree);
+
+ /**
+ * Helper method that determines if the specified container is open.
+ */
+ bool
+ IsContainerOpen(nsIXULTemplateResult* aResource);
+
+ /**
+ * A sorting callback for NS_QuickSort().
+ */
+ static int
+ Compare(const void* aLeft, const void* aRight, void* aClosure);
+
+ /**
+ * The real sort routine
+ */
+ int32_t
+ CompareResults(nsIXULTemplateResult* aLeft, nsIXULTemplateResult* aRight);
+
+ /**
+ * Sort the specified subtree, and recursively sort any subtrees
+ * beneath it.
+ */
+ nsresult
+ SortSubtree(nsTreeRows::Subtree* aSubtree);
+
+ NS_IMETHOD
+ HasGeneratedContent(nsIRDFResource* aResource,
+ nsIAtom* aTag,
+ bool* aGenerated) override;
+
+ // GetInsertionLocations, ReplaceMatch and SynchronizeResult are inherited
+ // from nsXULTemplateBuilder
+
+ /**
+ * Return true if the result can be inserted into the template as a new
+ * row.
+ */
+ bool
+ GetInsertionLocations(nsIXULTemplateResult* aResult,
+ nsCOMArray<nsIContent>** aLocations) override;
+
+ /**
+ * Implement result replacement
+ */
+ virtual nsresult
+ ReplaceMatch(nsIXULTemplateResult* aOldResult,
+ nsTemplateMatch* aNewMatch,
+ nsTemplateRule* aNewMatchRule,
+ void* aContext) override;
+
+ /**
+ * Implement match synchronization
+ */
+ virtual nsresult
+ SynchronizeResult(nsIXULTemplateResult* aResult) override;
+
+ /**
+ * The tree's box object, used to communicate with the front-end.
+ */
+ nsCOMPtr<nsITreeBoxObject> mBoxObject;
+
+ /**
+ * The tree's selection object.
+ */
+ nsCOMPtr<nsITreeSelection> mSelection;
+
+ /**
+ * The datasource that's used to persist open folder information
+ */
+ nsCOMPtr<nsIRDFDataSource> mPersistStateStore;
+
+ /**
+ * The rows in the view
+ */
+ nsTreeRows mRows;
+
+ /**
+ * The currently active sort variable
+ */
+ nsCOMPtr<nsIAtom> mSortVariable;
+
+ enum Direction {
+ eDirection_Descending = -1,
+ eDirection_Natural = 0,
+ eDirection_Ascending = +1
+ };
+
+ /**
+ * The currently active sort order
+ */
+ Direction mSortDirection;
+
+ /*
+ * Sort hints (compare case, etc)
+ */
+ uint32_t mSortHints;
+
+ /**
+ * The builder observers.
+ */
+ nsCOMArray<nsIXULTreeBuilderObserver> mObservers;
+
+ /*
+ * XUL store for holding open container state
+ */
+ nsCOMPtr<nsIXULStore> mLocalStore;
+};
+
+//----------------------------------------------------------------------
+
+nsresult
+NS_NewXULTreeBuilder(nsISupports* aOuter, REFNSIID aIID, void** aResult)
+{
+ *aResult = nullptr;
+
+ NS_PRECONDITION(aOuter == nullptr, "no aggregation");
+ if (aOuter)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsresult rv;
+ nsXULTreeBuilder* result = new nsXULTreeBuilder();
+ NS_ADDREF(result); // stabilize
+
+ rv = result->InitGlobals();
+
+ if (NS_SUCCEEDED(rv))
+ rv = result->QueryInterface(aIID, aResult);
+
+ NS_RELEASE(result);
+ return rv;
+}
+
+NS_IMPL_ADDREF_INHERITED(nsXULTreeBuilder, nsXULTemplateBuilder)
+NS_IMPL_RELEASE_INHERITED(nsXULTreeBuilder, nsXULTemplateBuilder)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(nsXULTreeBuilder, nsXULTemplateBuilder,
+ mBoxObject,
+ mSelection,
+ mPersistStateStore,
+ mLocalStore,
+ mObservers)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsXULTreeBuilder)
+ NS_INTERFACE_MAP_ENTRY(nsIXULTreeBuilder)
+ NS_INTERFACE_MAP_ENTRY(nsITreeView)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(XULTreeBuilder)
+NS_INTERFACE_MAP_END_INHERITING(nsXULTemplateBuilder)
+
+
+nsXULTreeBuilder::nsXULTreeBuilder()
+ : mSortDirection(eDirection_Natural), mSortHints(0)
+{
+}
+
+nsXULTreeBuilder::~nsXULTreeBuilder()
+{
+}
+
+void
+nsXULTreeBuilder::Uninit(bool aIsFinal)
+{
+ int32_t count = mRows.Count();
+ mRows.Clear();
+
+ if (mBoxObject) {
+ mBoxObject->BeginUpdateBatch();
+ mBoxObject->RowCountChanged(0, -count);
+ if (mBoxObject) {
+ mBoxObject->EndUpdateBatch();
+ }
+ }
+
+ nsXULTemplateBuilder::Uninit(aIsFinal);
+}
+
+
+//----------------------------------------------------------------------
+//
+// nsIXULTreeBuilder methods
+//
+
+NS_IMETHODIMP
+nsXULTreeBuilder::GetResourceAtIndex(int32_t aRowIndex, nsIRDFResource** aResult)
+{
+ if (aRowIndex < 0 || aRowIndex >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ return GetResourceFor(aRowIndex, aResult);
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::GetIndexOfResource(nsIRDFResource* aResource, int32_t* aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResource);
+ nsTreeRows::iterator iter = mRows.FindByResource(aResource);
+ if (iter == mRows.Last())
+ *aResult = -1;
+ else
+ *aResult = iter.GetRowIndex();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::AddObserver(nsIXULTreeBuilderObserver* aObserver)
+{
+ return mObservers.AppendObject(aObserver) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::RemoveObserver(nsIXULTreeBuilderObserver* aObserver)
+{
+ return mObservers.RemoveObject(aObserver) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::Sort(nsIDOMElement* aElement)
+{
+ nsCOMPtr<nsIContent> header = do_QueryInterface(aElement);
+ if (! header)
+ return NS_ERROR_FAILURE;
+
+ if (header->AttrValueIs(kNameSpaceID_None, nsGkAtoms::sortLocked,
+ nsGkAtoms::_true, eCaseMatters))
+ return NS_OK;
+
+ nsAutoString sort;
+ header->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort);
+
+ if (sort.IsEmpty())
+ return NS_OK;
+
+ // Grab the new sort variable
+ mSortVariable = NS_Atomize(sort);
+
+ nsAutoString hints;
+ header->GetAttr(kNameSpaceID_None, nsGkAtoms::sorthints, hints);
+
+ bool hasNaturalState = true;
+ nsWhitespaceTokenizer tokenizer(hints);
+ while (tokenizer.hasMoreTokens()) {
+ const nsDependentSubstring& token(tokenizer.nextToken());
+ if (token.EqualsLiteral("comparecase"))
+ mSortHints |= nsIXULSortService::SORT_COMPARECASE;
+ else if (token.EqualsLiteral("integer"))
+ mSortHints |= nsIXULSortService::SORT_INTEGER;
+ else if (token.EqualsLiteral("twostate"))
+ hasNaturalState = false;
+ }
+
+ // Cycle the sort direction
+ nsAutoString dir;
+ header->GetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, dir);
+
+ if (dir.EqualsLiteral("ascending")) {
+ dir.AssignLiteral("descending");
+ mSortDirection = eDirection_Descending;
+ }
+ else if (hasNaturalState && dir.EqualsLiteral("descending")) {
+ dir.AssignLiteral("natural");
+ mSortDirection = eDirection_Natural;
+ }
+ else {
+ dir.AssignLiteral("ascending");
+ mSortDirection = eDirection_Ascending;
+ }
+
+ // Sort it.
+ SortSubtree(mRows.GetRoot());
+ mRows.InvalidateCachedRow();
+ if (mBoxObject)
+ mBoxObject->Invalidate();
+
+ nsTreeUtils::UpdateSortIndicators(header, dir);
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+//
+// nsITreeView methods
+//
+
+NS_IMETHODIMP
+nsXULTreeBuilder::GetRowCount(int32_t* aRowCount)
+{
+ *aRowCount = mRows.Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::GetSelection(nsITreeSelection** aSelection)
+{
+ NS_IF_ADDREF(*aSelection = mSelection.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::SetSelection(nsITreeSelection* aSelection)
+{
+ NS_ENSURE_TRUE(!aSelection ||
+ nsTreeContentView::CanTrustTreeSelection(aSelection),
+ NS_ERROR_DOM_SECURITY_ERR);
+ mSelection = aSelection;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::GetRowProperties(int32_t aIndex, nsAString& aProps)
+{
+ NS_PRECONDITION(aIndex >= 0 && aIndex < mRows.Count(), "bad row");
+ if (aIndex < 0 || aIndex >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIContent> row;
+ GetTemplateActionRowFor(aIndex, getter_AddRefs(row));
+ if (row) {
+ nsAutoString raw;
+ row->GetAttr(kNameSpaceID_None, nsGkAtoms::properties, raw);
+
+ if (!raw.IsEmpty()) {
+ SubstituteText(mRows[aIndex]->mMatch->mResult, raw, aProps);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::GetCellProperties(int32_t aRow, nsITreeColumn* aCol,
+ nsAString& aProps)
+{
+ NS_ENSURE_ARG_POINTER(aCol);
+ NS_PRECONDITION(aRow >= 0 && aRow < mRows.Count(), "bad row");
+ if (aRow < 0 || aRow >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIContent> cell;
+ GetTemplateActionCellFor(aRow, aCol, getter_AddRefs(cell));
+ if (cell) {
+ nsAutoString raw;
+ cell->GetAttr(kNameSpaceID_None, nsGkAtoms::properties, raw);
+
+ if (!raw.IsEmpty()) {
+ SubstituteText(mRows[aRow]->mMatch->mResult, raw, aProps);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::GetColumnProperties(nsITreeColumn* aCol, nsAString& aProps)
+{
+ NS_ENSURE_ARG_POINTER(aCol);
+ // XXX sortactive fu
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::IsContainer(int32_t aIndex, bool* aResult)
+{
+ NS_PRECONDITION(aIndex >= 0 && aIndex < mRows.Count(), "bad row");
+ if (aIndex < 0 || aIndex >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ nsTreeRows::iterator iter = mRows[aIndex];
+
+ bool isContainer;
+ iter->mMatch->mResult->GetIsContainer(&isContainer);
+
+ iter->mContainerType = isContainer
+ ? nsTreeRows::eContainerType_Container
+ : nsTreeRows::eContainerType_Noncontainer;
+
+ *aResult = (iter->mContainerType == nsTreeRows::eContainerType_Container);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::IsContainerOpen(int32_t aIndex, bool* aOpen)
+{
+ NS_PRECONDITION(aIndex >= 0 && aIndex < mRows.Count(), "bad row");
+ if (aIndex < 0 || aIndex >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ nsTreeRows::iterator iter = mRows[aIndex];
+
+ if (iter->mContainerState == nsTreeRows::eContainerState_Unknown) {
+ bool isOpen = IsContainerOpen(iter->mMatch->mResult);
+
+ iter->mContainerState = isOpen
+ ? nsTreeRows::eContainerState_Open
+ : nsTreeRows::eContainerState_Closed;
+ }
+
+ *aOpen = (iter->mContainerState == nsTreeRows::eContainerState_Open);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::IsContainerEmpty(int32_t aIndex, bool* aResult)
+{
+ NS_PRECONDITION(aIndex >= 0 && aIndex < mRows.Count(), "bad row");
+ if (aIndex < 0 || aIndex >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ nsTreeRows::iterator iter = mRows[aIndex];
+ NS_ASSERTION(iter->mContainerType == nsTreeRows::eContainerType_Container,
+ "asking for empty state on non-container");
+
+ // if recursion is disabled, pretend that the container is empty. This
+ // ensures that folders are still displayed as such, yet won't display
+ // their children
+ if ((mFlags & eDontRecurse) && (iter->mMatch->mResult != mRootResult)) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ if (iter->mContainerFill == nsTreeRows::eContainerFill_Unknown) {
+ bool isEmpty;
+ iter->mMatch->mResult->GetIsEmpty(&isEmpty);
+
+ iter->mContainerFill = isEmpty
+ ? nsTreeRows::eContainerFill_Empty
+ : nsTreeRows::eContainerFill_Nonempty;
+ }
+
+ *aResult = (iter->mContainerFill == nsTreeRows::eContainerFill_Empty);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::IsSeparator(int32_t aIndex, bool* aResult)
+{
+ NS_PRECONDITION(aIndex >= 0 && aIndex < mRows.Count(), "bad row");
+ if (aIndex < 0 || aIndex >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ nsAutoString type;
+ nsTreeRows::Row& row = *(mRows[aIndex]);
+ row.mMatch->mResult->GetType(type);
+
+ *aResult = type.EqualsLiteral("separator");
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::GetParentIndex(int32_t aRowIndex, int32_t* aResult)
+{
+ NS_PRECONDITION(aRowIndex >= 0 && aRowIndex < mRows.Count(), "bad row");
+ if (aRowIndex < 0 || aRowIndex >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ // Construct a path to the row
+ nsTreeRows::iterator iter = mRows[aRowIndex];
+
+ // The parent of the row will be at the top of the path
+ nsTreeRows::Subtree* parent = iter.GetParent();
+
+ // Now walk through our previous siblings, subtracting off each
+ // one's subtree size
+ int32_t index = iter.GetChildIndex();
+ while (--index >= 0)
+ aRowIndex -= mRows.GetSubtreeSizeFor(parent, index) + 1;
+
+ // Now the parent's index will be the first row's index, less one.
+ *aResult = aRowIndex - 1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::HasNextSibling(int32_t aRowIndex, int32_t aAfterIndex, bool* aResult)
+{
+ NS_PRECONDITION(aRowIndex >= 0 && aRowIndex < mRows.Count(), "bad row");
+ if (aRowIndex < 0 || aRowIndex >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ // Construct a path to the row
+ nsTreeRows::iterator iter = mRows[aRowIndex];
+
+ // The parent of the row will be at the top of the path
+ nsTreeRows::Subtree* parent = iter.GetParent();
+
+ // We have a next sibling if the child is not the last in the
+ // subtree.
+ *aResult = iter.GetChildIndex() != parent->Count() - 1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::GetLevel(int32_t aRowIndex, int32_t* aResult)
+{
+ NS_PRECONDITION(aRowIndex >= 0 && aRowIndex < mRows.Count(), "bad row");
+ if (aRowIndex < 0 || aRowIndex >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ // Construct a path to the row; the ``level'' is the path length
+ // less one.
+ nsTreeRows::iterator iter = mRows[aRowIndex];
+ *aResult = iter.GetDepth() - 1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::GetImageSrc(int32_t aRow, nsITreeColumn* aCol, nsAString& aResult)
+{
+ NS_ENSURE_ARG_POINTER(aCol);
+ NS_PRECONDITION(aRow >= 0 && aRow < mRows.Count(), "bad index");
+ if (aRow < 0 || aRow >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ // Find the <cell> that corresponds to the column we want.
+ nsCOMPtr<nsIContent> cell;
+ GetTemplateActionCellFor(aRow, aCol, getter_AddRefs(cell));
+ if (cell) {
+ nsAutoString raw;
+ cell->GetAttr(kNameSpaceID_None, nsGkAtoms::src, raw);
+
+ SubstituteText(mRows[aRow]->mMatch->mResult, raw, aResult);
+ }
+ else
+ aResult.Truncate();
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsXULTreeBuilder::GetProgressMode(int32_t aRow, nsITreeColumn* aCol, int32_t* aResult)
+{
+ NS_ENSURE_ARG_POINTER(aCol);
+ NS_PRECONDITION(aRow >= 0 && aRow < mRows.Count(), "bad index");
+ if (aRow < 0 || aRow >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ *aResult = nsITreeView::PROGRESS_NONE;
+
+ // Find the <cell> that corresponds to the column we want.
+ nsCOMPtr<nsIContent> cell;
+ GetTemplateActionCellFor(aRow, aCol, getter_AddRefs(cell));
+ if (cell) {
+ nsAutoString raw;
+ cell->GetAttr(kNameSpaceID_None, nsGkAtoms::mode, raw);
+
+ nsAutoString mode;
+ SubstituteText(mRows[aRow]->mMatch->mResult, raw, mode);
+
+ if (mode.EqualsLiteral("normal"))
+ *aResult = nsITreeView::PROGRESS_NORMAL;
+ else if (mode.EqualsLiteral("undetermined"))
+ *aResult = nsITreeView::PROGRESS_UNDETERMINED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::GetCellValue(int32_t aRow, nsITreeColumn* aCol, nsAString& aResult)
+{
+ NS_ENSURE_ARG_POINTER(aCol);
+ NS_PRECONDITION(aRow >= 0 && aRow < mRows.Count(), "bad index");
+ if (aRow < 0 || aRow >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ // Find the <cell> that corresponds to the column we want.
+ nsCOMPtr<nsIContent> cell;
+ GetTemplateActionCellFor(aRow, aCol, getter_AddRefs(cell));
+ if (cell) {
+ nsAutoString raw;
+ cell->GetAttr(kNameSpaceID_None, nsGkAtoms::value, raw);
+
+ SubstituteText(mRows[aRow]->mMatch->mResult, raw, aResult);
+ }
+ else
+ aResult.Truncate();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::GetCellText(int32_t aRow, nsITreeColumn* aCol, nsAString& aResult)
+{
+ NS_ENSURE_ARG_POINTER(aCol);
+ NS_PRECONDITION(aRow >= 0 && aRow < mRows.Count(), "bad index");
+ if (aRow < 0 || aRow >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ // Find the <cell> that corresponds to the column we want.
+ nsCOMPtr<nsIContent> cell;
+ GetTemplateActionCellFor(aRow, aCol, getter_AddRefs(cell));
+ if (cell) {
+ nsAutoString raw;
+ cell->GetAttr(kNameSpaceID_None, nsGkAtoms::label, raw);
+
+ SubstituteText(mRows[aRow]->mMatch->mResult, raw, aResult);
+
+ }
+ else
+ aResult.Truncate();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::SetTree(nsITreeBoxObject* aTree)
+{
+ mBoxObject = aTree;
+
+ // If this is teardown time, then we're done.
+ if (!mBoxObject) {
+ Uninit(false);
+ return NS_OK;
+ }
+ NS_ENSURE_TRUE(mRoot, NS_ERROR_NOT_INITIALIZED);
+
+ // Only use the XUL store if the root's principal is trusted.
+ bool isTrusted = false;
+ nsresult rv = IsSystemPrincipal(mRoot->NodePrincipal(), &isTrusted);
+ if (NS_SUCCEEDED(rv) && isTrusted) {
+ mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
+ if(NS_WARN_IF(!mLocalStore)){
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ }
+
+ Rebuild();
+
+ EnsureSortVariables();
+ if (mSortVariable)
+ SortSubtree(mRows.GetRoot());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::ToggleOpenState(int32_t aIndex)
+{
+ if (aIndex < 0 || aIndex >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ nsIXULTemplateResult* result = mRows[aIndex]->mMatch->mResult;
+ if (! result)
+ return NS_ERROR_FAILURE;
+
+ if (mFlags & eDontRecurse)
+ return NS_OK;
+
+ if (result && result != mRootResult) {
+ // don't open containers if child processing isn't allowed
+ bool mayProcessChildren;
+ nsresult rv = result->GetMayProcessChildren(&mayProcessChildren);
+ if (NS_FAILED(rv) || !mayProcessChildren)
+ return rv;
+ }
+
+ uint32_t count = mObservers.Count();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsIXULTreeBuilderObserver> observer = mObservers.SafeObjectAt(i);
+ if (observer)
+ observer->OnToggleOpenState(aIndex);
+ }
+
+ if (mLocalStore && mRoot) {
+ bool isOpen;
+ IsContainerOpen(aIndex, &isOpen);
+
+ nsIDocument* doc = mRoot->GetComposedDoc();
+ if (!doc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIURI* docURI = doc->GetDocumentURI();
+ nsTreeRows::Row& row = *(mRows[aIndex]);
+ nsAutoString nodeid;
+ nsresult rv = row.mMatch->mResult->GetId(nodeid);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoCString utf8uri;
+ rv = docURI->GetSpec(utf8uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ NS_ConvertUTF8toUTF16 uri(utf8uri);
+
+ if (isOpen) {
+ mLocalStore->RemoveValue(uri, nodeid, NS_LITERAL_STRING("open"));
+ CloseContainer(aIndex);
+ } else {
+ mLocalStore->SetValue(uri, nodeid, NS_LITERAL_STRING("open"),
+ NS_LITERAL_STRING("true"));
+
+ OpenContainer(aIndex, result);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::CycleHeader(nsITreeColumn* aCol)
+{
+ NS_ENSURE_ARG_POINTER(aCol);
+ nsCOMPtr<nsIDOMElement> element;
+ aCol->GetElement(getter_AddRefs(element));
+
+ nsAutoString id;
+ aCol->GetId(id);
+
+ uint32_t count = mObservers.Count();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsIXULTreeBuilderObserver> observer = mObservers.SafeObjectAt(i);
+ if (observer)
+ observer->OnCycleHeader(id.get(), element);
+ }
+
+ return Sort(element);
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::SelectionChanged()
+{
+ uint32_t count = mObservers.Count();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsIXULTreeBuilderObserver> observer = mObservers.SafeObjectAt(i);
+ if (observer)
+ observer->OnSelectionChanged();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::CycleCell(int32_t aRow, nsITreeColumn* aCol)
+{
+ NS_ENSURE_ARG_POINTER(aCol);
+
+ nsAutoString id;
+ aCol->GetId(id);
+
+ uint32_t count = mObservers.Count();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsIXULTreeBuilderObserver> observer = mObservers.SafeObjectAt(i);
+ if (observer)
+ observer->OnCycleCell(aRow, id.get());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::IsEditable(int32_t aRow, nsITreeColumn* aCol, bool* _retval)
+{
+ *_retval = true;
+ NS_ENSURE_ARG_POINTER(aCol);
+ NS_PRECONDITION(aRow >= 0 && aRow < mRows.Count(), "bad index");
+ if (aRow < 0 || aRow >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ // Find the <cell> that corresponds to the column we want.
+ nsCOMPtr<nsIContent> cell;
+ GetTemplateActionCellFor(aRow, aCol, getter_AddRefs(cell));
+ if (cell) {
+ nsAutoString raw;
+ cell->GetAttr(kNameSpaceID_None, nsGkAtoms::editable, raw);
+
+ nsAutoString editable;
+ SubstituteText(mRows[aRow]->mMatch->mResult, raw, editable);
+
+ if (editable.EqualsLiteral("false"))
+ *_retval = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::IsSelectable(int32_t aRow, nsITreeColumn* aCol, bool* _retval)
+{
+ NS_PRECONDITION(aRow >= 0 && aRow < mRows.Count(), "bad index");
+ if (aRow < 0 || aRow >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ *_retval = true;
+
+ // Find the <cell> that corresponds to the column we want.
+ nsCOMPtr<nsIContent> cell;
+ GetTemplateActionCellFor(aRow, aCol, getter_AddRefs(cell));
+ if (cell) {
+ nsAutoString raw;
+ cell->GetAttr(kNameSpaceID_None, nsGkAtoms::selectable, raw);
+
+ nsAutoString selectable;
+ SubstituteText(mRows[aRow]->mMatch->mResult, raw, selectable);
+
+ if (selectable.EqualsLiteral("false"))
+ *_retval = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::SetCellValue(int32_t aRow, nsITreeColumn* aCol, const nsAString& aValue)
+{
+ NS_ENSURE_ARG_POINTER(aCol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::SetCellText(int32_t aRow, nsITreeColumn* aCol, const nsAString& aValue)
+{
+ NS_ENSURE_ARG_POINTER(aCol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::PerformAction(const char16_t* aAction)
+{
+ uint32_t count = mObservers.Count();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsIXULTreeBuilderObserver> observer = mObservers.SafeObjectAt(i);
+ if (observer)
+ observer->OnPerformAction(aAction);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::PerformActionOnRow(const char16_t* aAction, int32_t aRow)
+{
+ uint32_t count = mObservers.Count();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsIXULTreeBuilderObserver> observer = mObservers.SafeObjectAt(i);
+ if (observer)
+ observer->OnPerformActionOnRow(aAction, aRow);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::PerformActionOnCell(const char16_t* aAction, int32_t aRow, nsITreeColumn* aCol)
+{
+ NS_ENSURE_ARG_POINTER(aCol);
+ nsAutoString id;
+ aCol->GetId(id);
+
+ uint32_t count = mObservers.Count();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsIXULTreeBuilderObserver> observer = mObservers.SafeObjectAt(i);
+ if (observer)
+ observer->OnPerformActionOnCell(aAction, aRow, id.get());
+ }
+
+ return NS_OK;
+}
+
+
+void
+nsXULTreeBuilder::NodeWillBeDestroyed(const nsINode* aNode)
+{
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ mObservers.Clear();
+
+ nsXULTemplateBuilder::NodeWillBeDestroyed(aNode);
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::HasGeneratedContent(nsIRDFResource* aResource,
+ nsIAtom* aTag,
+ bool* aGenerated)
+{
+ *aGenerated = false;
+ NS_ENSURE_ARG_POINTER(aResource);
+
+ if (!mRootResult)
+ return NS_OK;
+
+ nsCOMPtr<nsIRDFResource> rootresource;
+ nsresult rv = mRootResult->GetResource(getter_AddRefs(rootresource));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (aResource == rootresource ||
+ mRows.FindByResource(aResource) != mRows.Last())
+ *aGenerated = true;
+
+ return NS_OK;
+}
+
+bool
+nsXULTreeBuilder::GetInsertionLocations(nsIXULTemplateResult* aResult,
+ nsCOMArray<nsIContent>** aLocations)
+{
+ *aLocations = nullptr;
+
+ // Get the reference point and check if it is an open container. Rows
+ // should not be generated otherwise.
+
+ nsAutoString ref;
+ nsresult rv = aResult->GetBindingFor(mRefVariable, ref);
+ if (NS_FAILED(rv) || ref.IsEmpty())
+ return false;
+
+ nsCOMPtr<nsIRDFResource> container;
+ rv = gRDFService->GetUnicodeResource(ref, getter_AddRefs(container));
+ if (NS_FAILED(rv))
+ return false;
+
+ // Can always insert into the root resource
+ if (container == mRows.GetRootResource())
+ return true;
+
+ nsTreeRows::iterator iter = mRows.FindByResource(container);
+ if (iter == mRows.Last())
+ return false;
+
+ return (iter->mContainerState == nsTreeRows::eContainerState_Open);
+}
+
+struct ResultComparator
+{
+ nsXULTreeBuilder* const mTreebuilder;
+ nsIXULTemplateResult* const mResult;
+ ResultComparator(nsXULTreeBuilder* aTreebuilder, nsIXULTemplateResult* aResult)
+ : mTreebuilder(aTreebuilder), mResult(aResult) {}
+ int operator()(const nsTreeRows::Row& aSubtree) const {
+ return mTreebuilder->CompareResults(mResult, aSubtree.mMatch->mResult);
+ }
+};
+
+nsresult
+nsXULTreeBuilder::ReplaceMatch(nsIXULTemplateResult* aOldResult,
+ nsTemplateMatch* aNewMatch,
+ nsTemplateRule* aNewMatchRule,
+ void *aLocation)
+{
+ if (! mBoxObject)
+ return NS_OK;
+
+ if (aOldResult) {
+ // Grovel through the rows looking for oldresult.
+ nsTreeRows::iterator iter = mRows.Find(aOldResult);
+
+ NS_ASSERTION(iter != mRows.Last(), "couldn't find row");
+ if (iter == mRows.Last())
+ return NS_ERROR_FAILURE;
+
+ // Remove the rows from the view
+ int32_t row = iter.GetRowIndex();
+
+ // If the row contains children, remove the matches from the
+ // children so that they can be regenerated again if the element
+ // gets added back.
+ int32_t delta = mRows.GetSubtreeSizeFor(iter);
+ if (delta)
+ RemoveMatchesFor(*(iter->mSubtree));
+
+ if (mRows.RemoveRowAt(iter) == 0 && iter.GetRowIndex() >= 0) {
+
+ // In this case iter now points to its parent
+ // Invalidate the row's cached fill state
+ iter->mContainerFill = nsTreeRows::eContainerFill_Unknown;
+
+ nsCOMPtr<nsITreeColumns> cols;
+ mBoxObject->GetColumns(getter_AddRefs(cols));
+ if (cols) {
+ nsCOMPtr<nsITreeColumn> primaryCol;
+ cols->GetPrimaryColumn(getter_AddRefs(primaryCol));
+ if (primaryCol)
+ mBoxObject->InvalidateCell(iter.GetRowIndex(), primaryCol);
+ }
+ }
+
+ // Notify the box object
+ mBoxObject->RowCountChanged(row, -delta - 1);
+ }
+
+ if (aNewMatch && aNewMatch->mResult) {
+ // Insertion.
+ int32_t row = -1;
+ nsTreeRows::Subtree* parent = nullptr;
+ nsIXULTemplateResult* result = aNewMatch->mResult;
+
+ nsAutoString ref;
+ nsresult rv = result->GetBindingFor(mRefVariable, ref);
+ if (NS_FAILED(rv) || ref.IsEmpty())
+ return rv;
+
+ nsCOMPtr<nsIRDFResource> container;
+ rv = gRDFService->GetUnicodeResource(ref, getter_AddRefs(container));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (container != mRows.GetRootResource()) {
+ nsTreeRows::iterator iter = mRows.FindByResource(container);
+ row = iter.GetRowIndex();
+
+ NS_ASSERTION(iter != mRows.Last(), "couldn't find container row");
+ if (iter == mRows.Last())
+ return NS_ERROR_FAILURE;
+
+ // Use the persist store to remember if the container
+ // is open or closed.
+ bool open = false;
+ IsContainerOpen(row, &open);
+
+ // If it's open, make sure that we've got a subtree structure ready.
+ if (open)
+ parent = mRows.EnsureSubtreeFor(iter);
+
+ // We know something has just been inserted into the
+ // container, so whether its open or closed, make sure
+ // that we've got our tree row's state correct.
+ if ((iter->mContainerType != nsTreeRows::eContainerType_Container) ||
+ (iter->mContainerFill != nsTreeRows::eContainerFill_Nonempty)) {
+ iter->mContainerType = nsTreeRows::eContainerType_Container;
+ iter->mContainerFill = nsTreeRows::eContainerFill_Nonempty;
+ mBoxObject->InvalidateRow(iter.GetRowIndex());
+ }
+ }
+ else {
+ parent = mRows.GetRoot();
+ }
+
+ if (parent) {
+ // If we get here, then we're inserting into an open
+ // container. By default, place the new element at the
+ // end of the container
+ size_t index = parent->Count();
+
+ if (mSortVariable) {
+ // Figure out where to put the new element through
+ // binary search.
+ mozilla::BinarySearchIf(*parent, 0, parent->Count(),
+ ResultComparator(this, result), &index);
+ }
+
+ nsTreeRows::iterator iter =
+ mRows.InsertRowAt(aNewMatch, parent, index);
+
+ mBoxObject->RowCountChanged(iter.GetRowIndex(), +1);
+
+ // See if this newly added row is open; in which case,
+ // recursively add its children to the tree, too.
+
+ if (mFlags & eDontRecurse)
+ return NS_OK;
+
+ if (result != mRootResult) {
+ // don't open containers if child processing isn't allowed
+ bool mayProcessChildren;
+ nsresult rv = result->GetMayProcessChildren(&mayProcessChildren);
+ if (NS_FAILED(rv) || ! mayProcessChildren) return NS_OK;
+ }
+
+ if (IsContainerOpen(result)) {
+ OpenContainer(iter.GetRowIndex(), result);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTreeBuilder::SynchronizeResult(nsIXULTemplateResult* aResult)
+{
+ if (mBoxObject) {
+ // XXX we could be more conservative and just invalidate the cells
+ // that got whacked...
+
+ nsTreeRows::iterator iter = mRows.Find(aResult);
+
+ NS_ASSERTION(iter != mRows.Last(), "couldn't find row");
+ if (iter == mRows.Last())
+ return NS_ERROR_FAILURE;
+
+ int32_t row = iter.GetRowIndex();
+ if (row >= 0)
+ mBoxObject->InvalidateRow(row);
+
+ MOZ_LOG(gXULTemplateLog, LogLevel::Debug,
+ ("xultemplate[%p] => row %d", this, row));
+ }
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+
+nsresult
+nsXULTreeBuilder::EnsureSortVariables()
+{
+ // Grovel through <treecols> kids to find the <treecol>
+ // with the sort attributes.
+ nsCOMPtr<nsIContent> treecols;
+
+ nsXULContentUtils::FindChildByTag(mRoot, kNameSpaceID_XUL,
+ nsGkAtoms::treecols,
+ getter_AddRefs(treecols));
+
+ if (!treecols)
+ return NS_OK;
+
+ for (nsIContent* child = treecols->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+
+ if (child->NodeInfo()->Equals(nsGkAtoms::treecol,
+ kNameSpaceID_XUL)) {
+ if (child->AttrValueIs(kNameSpaceID_None, nsGkAtoms::sortActive,
+ nsGkAtoms::_true, eCaseMatters)) {
+ nsAutoString sort;
+ child->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort);
+ if (! sort.IsEmpty()) {
+ mSortVariable = NS_Atomize(sort);
+
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::ascending, &nsGkAtoms::descending, nullptr};
+ switch (child->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::sortDirection,
+ strings, eCaseMatters)) {
+ case 0: mSortDirection = eDirection_Ascending; break;
+ case 1: mSortDirection = eDirection_Descending; break;
+ default: mSortDirection = eDirection_Natural; break;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTreeBuilder::RebuildAll()
+{
+ NS_ENSURE_TRUE(mRoot, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsIDocument> doc = mRoot->GetComposedDoc();
+
+ // Bail out early if we are being torn down.
+ if (!doc)
+ return NS_OK;
+
+ if (! mQueryProcessor)
+ return NS_OK;
+
+ if (mBoxObject) {
+ mBoxObject->BeginUpdateBatch();
+ }
+
+ if (mQueriesCompiled) {
+ Uninit(false);
+ }
+ else if (mBoxObject) {
+ int32_t count = mRows.Count();
+ mRows.Clear();
+ mBoxObject->RowCountChanged(0, -count);
+ }
+
+ nsresult rv = CompileQueries();
+ if (NS_SUCCEEDED(rv) && mQuerySets.Length() > 0) {
+ // Seed the rule network with assignments for the tree row variable
+ nsAutoString ref;
+ mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::ref, ref);
+ if (!ref.IsEmpty()) {
+ rv = mQueryProcessor->TranslateRef(mDataSource, ref,
+ getter_AddRefs(mRootResult));
+ if (NS_SUCCEEDED(rv) && mRootResult) {
+ OpenContainer(-1, mRootResult);
+
+ nsCOMPtr<nsIRDFResource> rootResource;
+ GetResultResource(mRootResult, getter_AddRefs(rootResource));
+
+ mRows.SetRootResource(rootResource);
+ }
+ }
+ }
+
+ if (mBoxObject) {
+ mBoxObject->EndUpdateBatch();
+ }
+
+ return rv;
+}
+
+nsresult
+nsXULTreeBuilder::GetTemplateActionRowFor(int32_t aRow, nsIContent** aResult)
+{
+ // Get the template in the DOM from which we're supposed to
+ // generate text
+ nsTreeRows::Row& row = *(mRows[aRow]);
+
+ // The match stores the indices of the rule and query to use. Use these
+ // to look up the right nsTemplateRule and use that rule's action to get
+ // the treerow in the template.
+ int16_t ruleindex = row.mMatch->RuleIndex();
+ if (ruleindex >= 0) {
+ nsTemplateQuerySet* qs = mQuerySets[row.mMatch->QuerySetPriority()];
+ nsTemplateRule* rule = qs->GetRuleAt(ruleindex);
+ if (rule) {
+ nsCOMPtr<nsIContent> children;
+ nsXULContentUtils::FindChildByTag(rule->GetAction(), kNameSpaceID_XUL,
+ nsGkAtoms::treechildren,
+ getter_AddRefs(children));
+ if (children) {
+ nsCOMPtr<nsIContent> item;
+ nsXULContentUtils::FindChildByTag(children, kNameSpaceID_XUL,
+ nsGkAtoms::treeitem,
+ getter_AddRefs(item));
+ if (item)
+ return nsXULContentUtils::FindChildByTag(item,
+ kNameSpaceID_XUL,
+ nsGkAtoms::treerow,
+ aResult);
+ }
+ }
+ }
+
+ *aResult = nullptr;
+ return NS_OK;
+}
+
+nsresult
+nsXULTreeBuilder::GetTemplateActionCellFor(int32_t aRow,
+ nsITreeColumn* aCol,
+ nsIContent** aResult)
+{
+ *aResult = nullptr;
+
+ if (!aCol) return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIContent> row;
+ GetTemplateActionRowFor(aRow, getter_AddRefs(row));
+ if (row) {
+ nsCOMPtr<nsIAtom> colAtom;
+ int32_t colIndex;
+ aCol->GetAtom(getter_AddRefs(colAtom));
+ aCol->GetIndex(&colIndex);
+
+ uint32_t j = 0;
+ for (nsIContent* child = row->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+
+ if (child->NodeInfo()->Equals(nsGkAtoms::treecell,
+ kNameSpaceID_XUL)) {
+ if (colAtom &&
+ child->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ref,
+ colAtom, eCaseMatters)) {
+ *aResult = child;
+ break;
+ }
+ else if (j == (uint32_t)colIndex)
+ *aResult = child;
+ j++;
+ }
+ }
+ }
+ NS_IF_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTreeBuilder::GetResourceFor(int32_t aRow, nsIRDFResource** aResource)
+{
+ nsTreeRows::Row& row = *(mRows[aRow]);
+ return GetResultResource(row.mMatch->mResult, aResource);
+}
+
+nsresult
+nsXULTreeBuilder::OpenContainer(int32_t aIndex, nsIXULTemplateResult* aResult)
+{
+ // A row index of -1 in this case means ``open tree body''
+ NS_ASSERTION(aIndex >= -1 && aIndex < mRows.Count(), "bad row");
+ if (aIndex < -1 || aIndex >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ nsTreeRows::Subtree* container;
+
+ if (aIndex >= 0) {
+ nsTreeRows::iterator iter = mRows[aIndex];
+ container = mRows.EnsureSubtreeFor(iter.GetParent(),
+ iter.GetChildIndex());
+
+ iter->mContainerState = nsTreeRows::eContainerState_Open;
+ }
+ else
+ container = mRows.GetRoot();
+
+ if (! container)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ int32_t count;
+ OpenSubtreeOf(container, aIndex, aResult, &count);
+
+ // Notify the box object
+ if (mBoxObject) {
+ if (aIndex >= 0)
+ mBoxObject->InvalidateRow(aIndex);
+
+ if (count)
+ mBoxObject->RowCountChanged(aIndex + 1, count);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTreeBuilder::OpenSubtreeOf(nsTreeRows::Subtree* aSubtree,
+ int32_t aIndex,
+ nsIXULTemplateResult *aResult,
+ int32_t* aDelta)
+{
+ AutoTArray<int32_t, 8> open;
+ int32_t count = 0;
+
+ int32_t rulecount = mQuerySets.Length();
+
+ for (int32_t r = 0; r < rulecount; r++) {
+ nsTemplateQuerySet* queryset = mQuerySets[r];
+ OpenSubtreeForQuerySet(aSubtree, aIndex, aResult, queryset, &count, open);
+ }
+
+ // Now recursively deal with any open sub-containers that just got
+ // inserted. We need to do this back-to-front to avoid skewing offsets.
+ for (int32_t i = open.Length() - 1; i >= 0; --i) {
+ int32_t index = open[i];
+
+ nsTreeRows::Subtree* child =
+ mRows.EnsureSubtreeFor(aSubtree, index);
+
+ nsIXULTemplateResult* result = (*aSubtree)[index].mMatch->mResult;
+
+ int32_t delta;
+ OpenSubtreeOf(child, aIndex + index, result, &delta);
+ count += delta;
+ }
+
+ // Sort the container.
+ if (mSortVariable) {
+ NS_QuickSort(mRows.GetRowsFor(aSubtree),
+ aSubtree->Count(),
+ sizeof(nsTreeRows::Row),
+ Compare,
+ this);
+ }
+
+ *aDelta = count;
+ return NS_OK;
+}
+
+nsresult
+nsXULTreeBuilder::OpenSubtreeForQuerySet(nsTreeRows::Subtree* aSubtree,
+ int32_t aIndex,
+ nsIXULTemplateResult* aResult,
+ nsTemplateQuerySet* aQuerySet,
+ int32_t* aDelta,
+ nsTArray<int32_t>& open)
+{
+ int32_t count = *aDelta;
+
+ nsCOMPtr<nsISimpleEnumerator> results;
+ nsresult rv = mQueryProcessor->GenerateResults(mDataSource, aResult,
+ aQuerySet->mCompiledQuery,
+ getter_AddRefs(results));
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool hasMoreResults;
+ rv = results->HasMoreElements(&hasMoreResults);
+
+ for (; NS_SUCCEEDED(rv) && hasMoreResults;
+ rv = results->HasMoreElements(&hasMoreResults)) {
+ nsCOMPtr<nsISupports> nr;
+ rv = results->GetNext(getter_AddRefs(nr));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIXULTemplateResult> nextresult = do_QueryInterface(nr);
+ if (!nextresult)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIRDFResource> resultid;
+ rv = GetResultResource(nextresult, getter_AddRefs(resultid));
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (! resultid)
+ continue;
+
+ // check if there is already an existing match. If so, a previous
+ // query already generated content so the match is just added to the
+ // end of the set of matches.
+
+ bool generateContent = true;
+
+ nsTemplateMatch* prevmatch = nullptr;
+ nsTemplateMatch* existingmatch = nullptr;
+ if (mMatchMap.Get(resultid, &existingmatch)){
+ // check if there is an existing match that matched a rule
+ while (existingmatch) {
+ if (existingmatch->IsActive())
+ generateContent = false;
+ prevmatch = existingmatch;
+ existingmatch = existingmatch->mNext;
+ }
+ }
+
+ nsTemplateMatch *newmatch =
+ nsTemplateMatch::Create(aQuerySet->Priority(), nextresult, nullptr);
+ if (!newmatch)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (generateContent) {
+ // Don't allow cyclic graphs to get our knickers in a knot.
+ bool cyclic = false;
+
+ if (aIndex >= 0) {
+ for (nsTreeRows::iterator iter = mRows[aIndex]; iter.GetDepth() > 0; iter.Pop()) {
+ nsCOMPtr<nsIRDFResource> parentid;
+ rv = GetResultResource(iter->mMatch->mResult, getter_AddRefs(parentid));
+ if (NS_FAILED(rv)) {
+ nsTemplateMatch::Destroy(newmatch, false);
+ return rv;
+ }
+
+ if (resultid == parentid) {
+ cyclic = true;
+ break;
+ }
+ }
+ }
+
+ if (cyclic) {
+ NS_WARNING("tree cannot handle cyclic graphs");
+ nsTemplateMatch::Destroy(newmatch, false);
+ continue;
+ }
+
+ int16_t ruleindex;
+ nsTemplateRule* matchedrule = nullptr;
+ rv = DetermineMatchedRule(nullptr, nextresult, aQuerySet,
+ &matchedrule, &ruleindex);
+ if (NS_FAILED(rv)) {
+ nsTemplateMatch::Destroy(newmatch, false);
+ return rv;
+ }
+
+ if (matchedrule) {
+ rv = newmatch->RuleMatched(aQuerySet, matchedrule, ruleindex,
+ nextresult);
+ if (NS_FAILED(rv)) {
+ nsTemplateMatch::Destroy(newmatch, false);
+ return rv;
+ }
+
+ // Remember that this match applied to this row
+ mRows.InsertRowAt(newmatch, aSubtree, count);
+
+ // If this is open, then remember it so we can recursively add
+ // *its* rows to the tree.
+ if (IsContainerOpen(nextresult)) {
+ if (open.AppendElement(count) == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ ++count;
+ }
+
+ if (mFlags & eLoggingEnabled)
+ OutputMatchToLog(resultid, newmatch, true);
+
+ }
+
+ if (prevmatch) {
+ prevmatch->mNext = newmatch;
+ }
+ else {
+ mMatchMap.Put(resultid, newmatch);
+ }
+ }
+
+ *aDelta = count;
+ return rv;
+}
+
+nsresult
+nsXULTreeBuilder::CloseContainer(int32_t aIndex)
+{
+ NS_ASSERTION(aIndex >= 0 && aIndex < mRows.Count(), "bad row");
+ if (aIndex < 0 || aIndex >= mRows.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ nsTreeRows::iterator iter = mRows[aIndex];
+
+ if (iter->mSubtree)
+ RemoveMatchesFor(*iter->mSubtree);
+
+
+ int32_t count = mRows.GetSubtreeSizeFor(iter);
+ mRows.RemoveSubtreeFor(iter);
+
+ iter->mContainerState = nsTreeRows::eContainerState_Closed;
+
+ if (mBoxObject) {
+ mBoxObject->InvalidateRow(aIndex);
+
+ if (count)
+ mBoxObject->RowCountChanged(aIndex + 1, -count);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXULTreeBuilder::RemoveMatchesFor(nsTreeRows::Subtree& subtree)
+{
+ for (int32_t i = subtree.Count() - 1; i >= 0; --i) {
+ nsTreeRows::Row& row = subtree[i];
+
+ nsTemplateMatch* match = row.mMatch;
+
+ nsCOMPtr<nsIRDFResource> id;
+ nsresult rv = GetResultResource(match->mResult, getter_AddRefs(id));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsTemplateMatch* existingmatch;
+ if (mMatchMap.Get(id, &existingmatch)) {
+ while (existingmatch) {
+ nsTemplateMatch* nextmatch = existingmatch->mNext;
+ nsTemplateMatch::Destroy(existingmatch, true);
+ existingmatch = nextmatch;
+ }
+
+ mMatchMap.Remove(id);
+ }
+
+ if ((row.mContainerState == nsTreeRows::eContainerState_Open) && row.mSubtree)
+ RemoveMatchesFor(*(row.mSubtree));
+ }
+
+ return NS_OK;
+}
+
+
+bool
+nsXULTreeBuilder::IsContainerOpen(nsIXULTemplateResult *aResult)
+{
+ // items are never open if recursion is disabled
+ if ((mFlags & eDontRecurse) && aResult != mRootResult) {
+ return false;
+ }
+
+ if (!mLocalStore) {
+ return false;
+ }
+
+ nsIDocument* doc = mRoot->GetComposedDoc();
+ if (!doc) {
+ return false;
+ }
+
+ nsIURI* docURI = doc->GetDocumentURI();
+
+ nsAutoString nodeid;
+ nsresult rv = aResult->GetId(nodeid);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsAutoCString utf8uri;
+ rv = docURI->GetSpec(utf8uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+ NS_ConvertUTF8toUTF16 uri(utf8uri);
+
+ nsAutoString val;
+ mLocalStore->GetValue(uri, nodeid, NS_LITERAL_STRING("open"), val);
+ return val.EqualsLiteral("true");
+}
+
+int
+nsXULTreeBuilder::Compare(const void* aLeft, const void* aRight, void* aClosure)
+{
+ nsXULTreeBuilder* self = static_cast<nsXULTreeBuilder*>(aClosure);
+
+ nsTreeRows::Row* left = static_cast<nsTreeRows::Row*>
+ (const_cast<void*>(aLeft));
+
+ nsTreeRows::Row* right = static_cast<nsTreeRows::Row*>
+ (const_cast<void*>(aRight));
+
+ return self->CompareResults(left->mMatch->mResult, right->mMatch->mResult);
+}
+
+int32_t
+nsXULTreeBuilder::CompareResults(nsIXULTemplateResult* aLeft, nsIXULTemplateResult* aRight)
+{
+ // this is an extra check done for RDF queries such that results appear in
+ // the order they appear in their containing Seq
+ if (mSortDirection == eDirection_Natural && mDB) {
+ // If the sort order is ``natural'', then see if the container
+ // is an RDF sequence. If so, we'll try to use the ordinal
+ // properties to determine order.
+ //
+ // XXX the problem with this is, it doesn't always get the
+ // *real* container; e.g.,
+ //
+ // <treerow uri="?uri" />
+ //
+ // <triple subject="?uri"
+ // predicate="http://home.netscape.com/NC-rdf#subheadings"
+ // object="?subheadings" />
+ //
+ // <member container="?subheadings" child="?subheading" />
+ //
+ // In this case mRefVariable is bound to ?uri, not
+ // ?subheadings. (The ``container'' in the template sense !=
+ // container in the RDF sense.)
+
+ nsCOMPtr<nsISupports> ref;
+ nsresult rv = aLeft->GetBindingObjectFor(mRefVariable, getter_AddRefs(ref));
+ if (NS_FAILED(rv))
+ return 0;
+
+ nsCOMPtr<nsIRDFResource> container = do_QueryInterface(ref);
+ if (container) {
+ bool isSequence = false;
+ gRDFContainerUtils->IsSeq(mDB, container, &isSequence);
+ if (isSequence) {
+ // Determine the indices of the left and right elements
+ // in the container.
+ int32_t lindex = 0, rindex = 0;
+
+ nsCOMPtr<nsIRDFResource> leftitem;
+ aLeft->GetResource(getter_AddRefs(leftitem));
+ if (leftitem) {
+ gRDFContainerUtils->IndexOf(mDB, container, leftitem, &lindex);
+ if (lindex < 0)
+ return 0;
+ }
+
+ nsCOMPtr<nsIRDFResource> rightitem;
+ aRight->GetResource(getter_AddRefs(rightitem));
+ if (rightitem) {
+ gRDFContainerUtils->IndexOf(mDB, container, rightitem, &rindex);
+ if (rindex < 0)
+ return 0;
+ }
+
+ return lindex - rindex;
+ }
+ }
+ }
+
+ int32_t sortorder;
+ if (!mQueryProcessor)
+ return 0;
+
+ mQueryProcessor->CompareResults(aLeft, aRight, mSortVariable, mSortHints, &sortorder);
+
+ if (sortorder)
+ sortorder = sortorder * mSortDirection;
+ return sortorder;
+}
+
+nsresult
+nsXULTreeBuilder::SortSubtree(nsTreeRows::Subtree* aSubtree)
+{
+ NS_QuickSort(mRows.GetRowsFor(aSubtree),
+ aSubtree->Count(),
+ sizeof(nsTreeRows::Row),
+ Compare,
+ this);
+
+ for (int32_t i = aSubtree->Count() - 1; i >= 0; --i) {
+ nsTreeRows::Subtree* child = (*aSubtree)[i].mSubtree;
+ if (child)
+ SortSubtree(child);
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsXULTreeBuilder::CanDrop(int32_t index, int32_t orientation,
+ nsIDOMDataTransfer* dataTransfer, bool *_retval)
+{
+ *_retval = false;
+ uint32_t count = mObservers.Count();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsIXULTreeBuilderObserver> observer = mObservers.SafeObjectAt(i);
+ if (observer) {
+ observer->CanDrop(index, orientation, dataTransfer, _retval);
+ if (*_retval)
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::Drop(int32_t row, int32_t orient, nsIDOMDataTransfer* dataTransfer)
+{
+ uint32_t count = mObservers.Count();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsIXULTreeBuilderObserver> observer = mObservers.SafeObjectAt(i);
+ if (observer) {
+ bool canDrop = false;
+ observer->CanDrop(row, orient, dataTransfer, &canDrop);
+ if (canDrop)
+ observer->OnDrop(row, orient, dataTransfer);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULTreeBuilder::IsSorted(bool *_retval)
+{
+ *_retval = (mSortVariable != nullptr);
+ return NS_OK;
+}
+
diff --git a/dom/xul/templates/tests/chrome/animals.rdf b/dom/xul/templates/tests/chrome/animals.rdf
new file mode 100644
index 000000000..06fee7ac5
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/animals.rdf
@@ -0,0 +1,224 @@
+<?xml version="1.0"?>
+
+<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:NC="http://home.netscape.com/NC-rdf#"
+ xmlns:ANIMALS="http://www.some-fictitious-zoo.com/rdf#">
+
+ <ANIMALS:Class RDF:about="http://www.some-fictitious-zoo.com/arachnids">
+ <ANIMALS:name>Arachnids</ANIMALS:name>
+ </ANIMALS:Class>
+
+ <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/arachnids/tarantula" ANIMALS:specimens="3">
+ <ANIMALS:name>Tarantula</ANIMALS:name>
+ <ANIMALS:species>Avicularia avicularia</ANIMALS:species>
+ </RDF:Description>
+
+ <ANIMALS:Class RDF:about="http://www.some-fictitious-zoo.com/birds">
+ <ANIMALS:name>Birds</ANIMALS:name>
+ <ANIMALS:keeper resource="http://www.some-fictitious-zoo.com/humans/sarah"/>
+ </ANIMALS:Class>
+
+ <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/birds/emu" ANIMALS:specimens="12">
+ <ANIMALS:name>Emu</ANIMALS:name>
+ <ANIMALS:species>Dromaius novaehollandiae</ANIMALS:species>
+ </RDF:Description>
+
+ <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/birds/barnowl" ANIMALS:specimens="4">
+ <ANIMALS:name>Barn Owl</ANIMALS:name>
+ <ANIMALS:species>Tyto alba</ANIMALS:species>
+ </RDF:Description>
+
+ <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/birds/raven" ANIMALS:specimens="0">
+ <ANIMALS:name>Raven</ANIMALS:name>
+ <ANIMALS:species>Corvus corax</ANIMALS:species>
+ </RDF:Description>
+
+ <ANIMALS:Class RDF:about="http://www.some-fictitious-zoo.com/crustaceans">
+ <ANIMALS:name>Crustaceans</ANIMALS:name>
+ <ANIMALS:keeper resource="http://www.some-fictitious-zoo.com/humans/robert"/>
+ </ANIMALS:Class>
+
+ <ANIMALS:Class RDF:about="http://www.some-fictitious-zoo.com/fish">
+ <ANIMALS:name>Fish</ANIMALS:name>
+ </ANIMALS:Class>
+
+ <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/fish/cod" ANIMALS:specimens="0">
+ <ANIMALS:name>Cod</ANIMALS:name>
+ <ANIMALS:species>Gadus morhua</ANIMALS:species>
+ </RDF:Description>
+
+ <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/fish/swordfish" ANIMALS:specimens="3">
+ <ANIMALS:name>Swordfish</ANIMALS:name>
+ <ANIMALS:species>Xiphias gladius</ANIMALS:species>
+ </RDF:Description>
+
+ <ANIMALS:Class RDF:about="http://www.some-fictitious-zoo.com/mammals">
+ <ANIMALS:name>Mammals</ANIMALS:name>
+ </ANIMALS:Class>
+
+ <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/mammals/lion">
+ <ANIMALS:name>Lion</ANIMALS:name>
+ <ANIMALS:species>Panthera leo</ANIMALS:species>
+ <ANIMALS:specimens NC:parseType="Integer">4</ANIMALS:specimens>
+ <ANIMALS:specimensAsString>4</ANIMALS:specimensAsString>
+ </RDF:Description>
+
+ <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/mammals/hippopotamus">
+ <ANIMALS:name>HIPPOPOTAMUS</ANIMALS:name>
+ <ANIMALS:species>Hippopotamus amphibius</ANIMALS:species>
+ <ANIMALS:specimens NC:parseType="Integer">2</ANIMALS:specimens>
+ <ANIMALS:specimensAsString>2</ANIMALS:specimensAsString>
+ </RDF:Description>
+
+ <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/mammals/africanelephant">
+ <ANIMALS:name>African Elephant</ANIMALS:name>
+ <ANIMALS:species>Loxodonta africana</ANIMALS:species>
+ <ANIMALS:specimens NC:parseType="Integer">14</ANIMALS:specimens>
+ <ANIMALS:specimensAsString>14</ANIMALS:specimensAsString>
+ </RDF:Description>
+
+ <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/mammals/llama">
+ <ANIMALS:name>LLAMA</ANIMALS:name>
+ <ANIMALS:species>Lama glama</ANIMALS:species>
+ <ANIMALS:specimens NC:parseType="Integer">5</ANIMALS:specimens>
+ <ANIMALS:specimensAsString>5</ANIMALS:specimensAsString>
+ </RDF:Description>
+
+ <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/mammals/polarbear">
+ <ANIMALS:name>Polar Bear</ANIMALS:name>
+ <ANIMALS:species>Thalarctos maritimus</ANIMALS:species>
+ <ANIMALS:specimens NC:parseType="Integer">20</ANIMALS:specimens>
+ <ANIMALS:specimensAsString>20</ANIMALS:specimensAsString>
+ </RDF:Description>
+
+ <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/mammals/aardvark">
+ <ANIMALS:name>aardvark</ANIMALS:name>
+ <ANIMALS:species>Orycteropus afer</ANIMALS:species>
+ <ANIMALS:specimens NC:parseType="Integer">2</ANIMALS:specimens>
+ <ANIMALS:specimensAsString>2</ANIMALS:specimensAsString>
+ </RDF:Description>
+
+ <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo">
+ <ANIMALS:name>Nine-banded Armadillo</ANIMALS:name>
+ <ANIMALS:species>Dasypus novemcinctus</ANIMALS:species>
+ <ANIMALS:specimens NC:parseType="Integer">1</ANIMALS:specimens>
+ <ANIMALS:specimensAsString>1</ANIMALS:specimensAsString>
+ </RDF:Description>
+
+ <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/mammals/gorilla">
+ <ANIMALS:name>Gorilla</ANIMALS:name>
+ <ANIMALS:species>Gorilla gorilla</ANIMALS:species>
+ <ANIMALS:specimens NC:parseType="Integer">7</ANIMALS:specimens>
+ <ANIMALS:specimensAsString>7</ANIMALS:specimensAsString>
+ </RDF:Description>
+
+ <ANIMALS:Class RDF:about="http://www.some-fictitious-zoo.com/reptiles">
+ <ANIMALS:name>Reptiles</ANIMALS:name>
+ <ANIMALS:keeper resource="http://www.some-fictitious-zoo.com/humans/robert"/>
+ </ANIMALS:Class>
+
+ <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/reptiles/anaconda" ANIMALS:specimens="1">
+ <ANIMALS:name>Anaconda</ANIMALS:name>
+ <ANIMALS:species>Eunectes murinus</ANIMALS:species>
+ </RDF:Description>
+
+ <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/reptiles/chameleon" ANIMALS:specimens="2">
+ <ANIMALS:name>Chameleon</ANIMALS:name>
+ <ANIMALS:species>Chamaeleo chamaelon</ANIMALS:species>
+ </RDF:Description>
+
+ <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/some-animals" ANIMALS:name="Zoo Animals">
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/arachnids"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/birds"/>
+ </RDF:Seq>
+
+ <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/all-animals" ANIMALS:name="Zoo Animals">
+ <RDF:li>
+ <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/arachnids">
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/arachnids/tarantula"/>
+ </RDF:Seq>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/birds">
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/birds/emu"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/birds/barnowl"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/birds/raven"/>
+ </RDF:Seq>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/crustaceans"/>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/fish">
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/fish/cod"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/fish/swordfish"/>
+ </RDF:Seq>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/mammals">
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/mammals/lion"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/mammals/hippopotamus"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/mammals/africanelephant"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/mammals/llama"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/mammals/polarbear"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/mammals/aardvark"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/mammals/gorilla"/>
+ </RDF:Seq>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/reptiles">
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/reptiles/anaconda"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/reptiles/chameleon"/>
+ </RDF:Seq>
+ </RDF:li>
+ </RDF:Seq>
+
+ <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/humans" ANIMALS:name="Humans">
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/humans/sarah"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/humans/robert"/>
+ </RDF:Seq>
+
+ <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/sarahs-pets" ANIMALS:name="Sarah's Pets">
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/birds/emu"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/arachnids/tarantula"/>
+ </RDF:Seq>
+
+ <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/roberts-pets" ANIMALS:name="Robert's Pets">
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/reptiles/chameleon"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/arachnids/tarantula"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/mammals/llama"/>
+ </RDF:Seq>
+
+ <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/humans/sarah" ANIMALS:name="Sarah">
+ <ANIMALS:pets resource="http://www.some-fictitious-zoo.com/sarahs-pets"/>
+ <ANIMALS:favoriteAnimal resource="http://www.some-fictitious-zoo.com/birds/emu"/>
+ <ANIMALS:favoriteAnimal resource="http://www.some-fictitious-zoo.com/mammals/polarbear"/>
+ <ANIMALS:favoriteAnimal resource="http://www.some-fictitious-zoo.com/arachnids/tarantula"/>
+ <ANIMALS:description>
+ Sarah became caretaker of the Fictitious Zoo's emu exhibit in 2001. With so
+ many emus living there, she has a lot to do!
+ </ANIMALS:description>
+ </RDF:Description>
+
+ <RDF:Description RDF:about="http://www.some-fictitious-zoo.com/humans/robert" ANIMALS:name="Robert">
+ <ANIMALS:pets resource="http://www.some-fictitious-zoo.com/roberts-pets"/>
+ <ANIMALS:favoriteAnimal resource="http://www.some-fictitious-zoo.com/arachnids/tarantula"/>
+ <ANIMALS:favoriteAnimal resource="http://www.some-fictitious-zoo.com/reptiles/anaconda"/>
+ <ANIMALS:favoriteAnimal resource="http://www.some-fictitious-zoo.com/reptiles/chameleon"/>
+ <ANIMALS:favoriteAnimal resource="http://www.some-fictitious-zoo.com/mammals/africanelephant"/>
+ <ANIMALS:description>
+ Robert helps visitors to the Fictitious Zoo's reptile pavilion learn
+ more about some of the more unusual creatures that live there.
+ </ANIMALS:description>
+ <ANIMALS:lastName>Sanderson</ANIMALS:lastName>
+ </RDF:Description>
+
+ <RDF:Seq RDF:about="http://www.some-fictitious-zoo.com/marked" ANIMALS:name="Marked">
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/humans/sarah"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/mammals"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/crustaceans"/>
+ <RDF:li RDF:resource="http://www.some-fictitious-zoo.com/birds/emu"/>
+ </RDF:Seq>
+
+</RDF:RDF>
diff --git a/dom/xul/templates/tests/chrome/animals.sqlite b/dom/xul/templates/tests/chrome/animals.sqlite
new file mode 100644
index 000000000..7ba88ff74
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/animals.sqlite
Binary files differ
diff --git a/dom/xul/templates/tests/chrome/animals.xml b/dom/xul/templates/tests/chrome/animals.xml
new file mode 100644
index 000000000..f73ec718b
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/animals.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+
+<!DOCTYPE zoo [
+ <!ATTLIST species id ID #REQUIRED>
+]>
+
+<zoo>
+ <class>
+ <name>Reptiles</name>
+ <species id="Chamaeleo-chamaelon" name="Chameleon" specimens="2"/>
+ </class>
+ <class>
+ <name>Birds</name>
+ <species id="Dromaius-novaehollandiae" name="Emu" specimens="12"/>
+ <species id="Tyto-alba" name="Barn Owl" specimens="4"/>
+ <species id="Corvus-corax" name="Raven" specimens="0"/>
+ <location>Aviary</location>
+ </class>
+</zoo>
diff --git a/dom/xul/templates/tests/chrome/bug441785-1.rdf b/dom/xul/templates/tests/chrome/bug441785-1.rdf
new file mode 100644
index 000000000..4be55657f
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/bug441785-1.rdf
@@ -0,0 +1,263 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:row="http://dummy/rdf#" xmlns:NC="http://home.netscape.com/NC-rdf#">
+ <RDF:Bag about="urn:data:row">
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111110</row:id>
+ <row:title>FILE 1 -- A</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111111</row:id>
+ <row:title>FILE 1 -- B</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111110</row:id>
+ <row:title>FILE 1 -- C</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111111</row:id>
+ <row:title>FILE 1 -- D</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111112</row:id>
+ <row:title>FILE 1 -- E</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111110</row:id>
+ <row:title>FILE 1 -- F</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111114</row:id>
+ <row:title>FILE 1 -- G</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111111</row:id>
+ <row:title>FILE 1 -- H</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111118</row:id>
+ <row:title>FILE 1 -- I</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111116</row:id>
+ <row:title>FILE 1 -- J</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111110</row:id>
+ <row:title>FILE 1 -- K</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111119</row:id>
+ <row:title>FILE 1 -- L</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111116</row:id>
+ <row:title>FILE 1 -- M</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">1111111</row:id>
+ <row:title>FILE 1 -- N</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111113</row:id>
+ <row:title>FILE 1 -- O</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111111</row:id>
+ <row:title>FILE 1 -- P</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111117</row:id>
+ <row:title>FILE 1 -- Q</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">1111110</row:id>
+ <row:title>FILE 1 -- R</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111113</row:id>
+ <row:title>FILE 1 -- S</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">1111112</row:id>
+ <row:title>FILE 1 -- T</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111116</row:id>
+ <row:title>FILE 1 -- U</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">1111111</row:id>
+ <row:title>FILE 1 -- V</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">1111121</row:id>
+ <row:title>FILE 1 -- W</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111114</row:id>
+ <row:title>FILE 1 -- X</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">1111122</row:id>
+ <row:title>FILE 1 -- Y</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">1111113</row:id>
+ <row:title>FILE 1 -- Z</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">1111119</row:id>
+ <row:title>FILE 1 -- AA</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">1111117</row:id>
+ <row:title>FILE 1 -- BB</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111117</row:id>
+ <row:title>FILE 1 -- CC</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111117</row:id>
+ <row:title>FILE 1 -- DD</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111116</row:id>
+ <row:title>FILE 1 -- EE</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">1111124</row:id>
+ <row:title>FILE 1 -- FF</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111118</row:id>
+ <row:title>FILE 1 -- GG</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">1111117</row:id>
+ <row:title>FILE 1 -- HH</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111118</row:id>
+ <row:title>FILE 1 -- II</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">1111125</row:id>
+ <row:title>FILE 1 -- JJ</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">1111124</row:id>
+ <row:title>FILE 1 -- KK</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">1111132</row:id>
+ <row:title>FILE 1 -- LL</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">1111136</row:id>
+ <row:title>FILE 1 -- MM</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">1111123</row:id>
+ <row:title>FILE 1 -- NN</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">1111122</row:id>
+ <row:title>FILE 1 -- OO</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">111110</row:id>
+ <row:title>FILE 1 -- PP</row:title>
+ </RDF:Description>
+ </RDF:li>
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">1111116</row:id>
+ <row:title>FILE 1 -- QQ</row:title>
+ </RDF:Description>
+ </RDF:li>
+ </RDF:Bag>
+</RDF:RDF>
diff --git a/dom/xul/templates/tests/chrome/bug441785-2.rdf b/dom/xul/templates/tests/chrome/bug441785-2.rdf
new file mode 100644
index 000000000..dca97ba78
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/bug441785-2.rdf
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:row="http://dummy/rdf#" xmlns:NC="http://home.netscape.com/NC-rdf#">
+ <RDF:Bag about="urn:data:row">
+ <RDF:li>
+ <RDF:Description>
+ <row:id NC:parseType="Integer">222220</row:id>
+ <row:title>FILE 2 -- A</row:title>
+ </RDF:Description>
+ </RDF:li>
+ </RDF:Bag>
+</RDF:RDF>
diff --git a/dom/xul/templates/tests/chrome/chrome.ini b/dom/xul/templates/tests/chrome/chrome.ini
new file mode 100644
index 000000000..97989723c
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/chrome.ini
@@ -0,0 +1,225 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ animals.rdf
+ animals.sqlite
+ animals.xml
+ bug441785-1.rdf
+ bug441785-2.rdf
+ templates_shared.js
+
+[test_bug329335.xul]
+[test_bug330010.xul]
+skip-if = os == "win"
+support-files = file_bug330010.rdf
+[test_bug397148.xul]
+skip-if = true # Bug 879531
+[test_bug441785.xul]
+[test_bug476634.xul]
+[test_sortservice.xul]
+[test_tmpl_bindingsextendedsyntax.xul]
+[test_tmpl_bindingsmultiple.xul]
+[test_tmpl_bindingsquerysyntax.xul]
+[test_tmpl_bindingsreversed.xul]
+[test_tmpl_bindingssameastriple.xul]
+[test_tmpl_containerandmembervariablechanged.xul]
+[test_tmpl_containervariablechanged.xul]
+[test_tmpl_containmentattribute.xul]
+[test_tmpl_defaultcontainervariableisuri.xul]
+[test_tmpl_errors.xul]
+[test_tmpl_extendedsyntax.xul]
+[test_tmpl_extendedsyntaxemptyconditions.xul]
+[test_tmpl_extendedsyntaxotherrefvariable.xul]
+[test_tmpl_extendedsyntaxremoveunmatched.xul]
+[test_tmpl_extendedsyntaxsimplevariablesubstitution.xul]
+[test_tmpl_extendedsyntaxtworulesrecurse.xul]
+[test_tmpl_extendedsyntaxusinganinterveningcontainer.xul]
+[test_tmpl_extendedvariablesubstitution.xul]
+[test_tmpl_gridelement.xul]
+[test_tmpl_htmlelementextendedsyntaxwithbinding.xul]
+[test_tmpl_htmlelementquerysyntaxrecursive.xul]
+[test_tmpl_htmlelementquerysyntaxwithmultiplerules.xul]
+[test_tmpl_htmlelementsimplesyntax.xul]
+[test_tmpl_htmlelementsimplesyntaxusingatextnode.xul]
+[test_tmpl_invalidqp.xul]
+[test_tmpl_listboxelement.xul]
+[test_tmpl_literalasmember.xul]
+[test_tmpl_membervariablechanged.xul]
+[test_tmpl_membervariablesubstitution.xul]
+[test_tmpl_menuelement.xul]
+[test_tmpl_menuelementrecursive.xul]
+[test_tmpl_menulistelement.xul]
+[test_tmpl_mixedsyntaxiscontainer.xul]
+[test_tmpl_mixedsyntaxiscontainerisempty.xul]
+[test_tmpl_mixedsyntaxisempty.xul]
+[test_tmpl_noaction.xul]
+[test_tmpl_noactionuriattribute.xul]
+[test_tmpl_parentconditions.xul]
+[test_tmpl_parentcontenttag.xul]
+[test_tmpl_parentsimplesyntax.xul]
+[test_tmpl_query3triples.xul]
+[test_tmpl_query3tripleswherecontains.xul]
+[test_tmpl_querymember3tripleswhereequals.xul]
+[test_tmpl_querymemberandtwotriples.xul]
+[test_tmpl_querymembertriplemembertriple.xul]
+[test_tmpl_queryresourcematch.xul]
+[test_tmpl_queryreversetriple.xul]
+[test_tmpl_queryselfwithtriple.xul]
+[test_tmpl_querysetone.xul]
+[test_tmpl_querysettwo.xul]
+[test_tmpl_querysettwowithcondition.xul]
+[test_tmpl_querysyntax.xul]
+[test_tmpl_querysyntaxmultiplerules.xul]
+[test_tmpl_querysyntaxmultiplerulesfirstconditionall.xul]
+[test_tmpl_querysyntaxmultiplerulestwoconditions.xul]
+[test_tmpl_querytripleandmembermerge.xul]
+[test_tmpl_querytripleobjecttosubject.xul]
+[test_tmpl_querytwomembers.xul]
+[test_tmpl_querytwomembersfiltered.xul]
+[test_tmpl_querytwotriples.xul]
+[test_tmpl_queryupwardsmember.xul]
+[test_tmpl_queryupwardsmembertripleandfilteringtriple.xul]
+[test_tmpl_querywithemptyconditions.xul]
+[test_tmpl_referenceasmember.xul]
+[test_tmpl_regenerate.xul]
+[test_tmpl_selfgenerationextendedsyntax.xul]
+[test_tmpl_selfgenerationsimplesyntax.xul]
+[test_tmpl_simplesyntaxenclosedinacontainer.xul]
+[test_tmpl_simplesyntaxenclosedinacontainerwitharule.xul]
+[test_tmpl_simplesyntaxfilter.xul]
+[test_tmpl_simplesyntaxfilterwithmultiplerules.xul]
+[test_tmpl_simplesyntaxfilterwithrule.xul]
+[test_tmpl_simplesyntaxiteratingoverasinglevalue.xul]
+[test_tmpl_simplesyntaxusinganinterveningcontainer.xul]
+[test_tmpl_simplesyntaxusingatextnode.xul]
+[test_tmpl_simplesyntaxusingcontainerasthegenerationelement.xul]
+[test_tmpl_simplesyntaxusingdontrecurse.xul]
+[test_tmpl_simplesyntaxusingrecursivegeneration.xul]
+[test_tmpl_simplesyntaxusingrecursivegenerationagain.xul]
+[test_tmpl_simplesyntaxwithtwovariablesused.xul]
+[test_tmpl_simplevariablesubstitutioncaretsatbeginningandend.xul]
+[test_tmpl_simplevariablesubstitutioncaretsubstitution.xul]
+[test_tmpl_simplevariablesubstitutionnovariable.xul]
+[test_tmpl_simplevariablesubstitutionquestionmarkaspartofvariable.xul]
+[test_tmpl_simplevariablesubstitutionquestionmarksubstitution.xul]
+[test_tmpl_simplevariablesubstitutiontextandvariable.xul]
+[test_tmpl_simplevariablesubstitutionvariableandtextconcatenated.xul]
+[test_tmpl_simplevariablesubstitutionvariablesconcatenated.xul]
+[test_tmpl_sortascendinginteger.xul]
+[test_tmpl_sortascendingquerysyntax.xul]
+[test_tmpl_sortascendingtworulesquerysyntax.xul]
+[test_tmpl_sortascendingtworuleswithcontainerquerysyntax.xul]
+[test_tmpl_sortascendingtworuleswithdifferentcontainerquerysyntax.xul]
+[test_tmpl_sortdescendingquerysyntax.xul]
+[test_tmpl_sortquerymemberandtwotriples.xul]
+[test_tmpl_sortresource2descendingsimplesyntax.xul]
+[test_tmpl_sortresource2settopredicateascendingquerysyntax.xul]
+[test_tmpl_sortresource2settopredicatedescendingquerysyntax.xul]
+[test_tmpl_sortresourceascendingquerysyntax.xul]
+[test_tmpl_sortresourcedescendingquerysyntax.xul]
+[test_tmpl_sortresourcesettopredicateascendingquerysyntax.xul]
+[test_tmpl_sortresourcesettopredicatedescendingquerysyntax.xul]
+[test_tmpl_sorttworesourcesasstringsettopredicatedescendingquerysyntax.xul]
+[test_tmpl_sorttworesourcessettopredicateascendingquerysyntax.xul]
+[test_tmpl_sorttwovariablesascendingquerysyntax.xul]
+[test_tmpl_sorttwovariablesascendingsimplesyntax.xul]
+[test_tmpl_sorttwovariablesdescendingquerysyntax.xul]
+[test_tmpl_sortunknownascendingquerysyntax.xul]
+[test_tmpl_storage_bad_parameters.xul]
+[test_tmpl_storage_bad_parameters_2.xul]
+[test_tmpl_storage_bad_parameters_3.xul]
+[test_tmpl_storage_baddatasource.xul]
+[test_tmpl_storage_badquery.xul]
+[test_tmpl_storage_dynamicparameters.xul]
+[test_tmpl_storage_listbox.xul]
+[test_tmpl_storage_multiqueries.xul]
+[test_tmpl_storage_parameters.xul]
+[test_tmpl_storage_rule.xul]
+[test_tmpl_storage_simple.xul]
+[test_tmpl_storage_sortintegerasc.xul]
+[test_tmpl_storage_sortintegerdesc.xul]
+[test_tmpl_storage_sortstringasc.xul]
+[test_tmpl_storage_sortstringdesc.xul]
+[test_tmpl_storage_tree.xul]
+[test_tmpl_treeelementquerysyntax.xul]
+[test_tmpl_treeelementquerysyntaxnotrecursive.xul]
+[test_tmpl_treeelementquerysyntaxnotrecursivetreebuilder.xul]
+[test_tmpl_treeelementquerysyntaxrecursive.xul]
+[test_tmpl_treeelementquerysyntaxrecursivemultiplerules.xul]
+[test_tmpl_treeelementquerysyntaxrecursivemultiplerulestreebuilder.xul]
+[test_tmpl_treeelementquerysyntaxrecursivetreebuilder.xul]
+[test_tmpl_treeelementquerysyntaxtreebuilder.xul]
+[test_tmpl_treeelementsimplesyntaxnotrecursive.xul]
+[test_tmpl_treeelementsimplesyntaxnotrecursivetreebuilder.xul]
+[test_tmpl_treeelementsimplesyntaxrecursive.xul]
+[test_tmpl_treeelementsimplesyntaxrecursivetreebuilder.xul]
+[test_tmpl_treeelementtreecell.xul]
+[test_tmpl_treeelementtreecellsortascending.xul]
+[test_tmpl_treeelementtreecellsortascendingtreebuilder.xul]
+[test_tmpl_treeelementtreecelltreebuilder.xul]
+[test_tmpl_treeelementtreeitemonly.xul]
+[test_tmpl_treeelementtreeitemsortascending.xul]
+[test_tmpl_twogenerationnodes.xul]
+[test_tmpl_whereafterignorecase.xul]
+[test_tmpl_whereafterlowercase.xul]
+[test_tmpl_whereafternegation.xul]
+[test_tmpl_whereafteruppercase.xul]
+[test_tmpl_wherebeforeignorecase.xul]
+[test_tmpl_wherebeforelowercase.xul]
+[test_tmpl_wherebeforenegation.xul]
+[test_tmpl_wherebeforeuppercase.xul]
+[test_tmpl_wherecontains.xul]
+[test_tmpl_wherecontainsignorecase.xul]
+[test_tmpl_wherecontainsnegation.xul]
+[test_tmpl_wherecontainsnumber.xul]
+[test_tmpl_wherecontainsnumberstring.xul]
+[test_tmpl_wherecontainsresource.xul]
+[test_tmpl_wherecontainstwo.xul]
+[test_tmpl_whereendswith.xul]
+[test_tmpl_whereendswithignorecase.xul]
+[test_tmpl_whereendswithnegation.xul]
+[test_tmpl_whereequals.xul]
+[test_tmpl_whereequalsignorecase.xul]
+[test_tmpl_whereequalsmultiple.xul]
+[test_tmpl_whereequalsmultiplenegation.xul]
+[test_tmpl_whereequalsmultiplenegationignorecase.xul]
+[test_tmpl_whereequalsnegation.xul]
+[test_tmpl_whereequalsnegationignorecase.xul]
+[test_tmpl_whereequalsnegationwrongcase.xul]
+[test_tmpl_whereequalsnumber.xul]
+[test_tmpl_whereequalsothervariable.xul]
+[test_tmpl_whereequalsresource.xul]
+[test_tmpl_whereequalssamevariable.xul]
+[test_tmpl_whereequalswrongcase.xul]
+[test_tmpl_wheregreater.xul]
+[test_tmpl_wheregreaternegation.xul]
+[test_tmpl_wheregreaternegationstring.xul]
+[test_tmpl_wheregreaterstring.xul]
+[test_tmpl_whereless.xul]
+[test_tmpl_wherelessnegation.xul]
+[test_tmpl_wherelessnegationstring.xul]
+[test_tmpl_wherelessstring.xul]
+[test_tmpl_wherenorel.xul]
+[test_tmpl_wherenosubject.xul]
+[test_tmpl_wherenovalue.xul]
+[test_tmpl_wherestartswith.xul]
+[test_tmpl_wherestartswithignorecase.xul]
+[test_tmpl_wherestartswithmultiple.xul]
+[test_tmpl_wherestartswithnegation.xul]
+[test_tmpl_wherestartswithunknownvariable.xul]
+[test_tmpl_wherestartswithvariable.xul]
+[test_tmpl_wheresubjectequalsvariable.xul]
+[test_tmpl_wheresubjectstartswithvariable.xul]
+[test_tmpl_xmlquerysimple.xul]
+[test_tmpl_xmlquerywithassign.xul]
+[test_tmpl_xmlquerywithassignmentandcondition.xul]
+[test_tmpl_xmlquerywithassignmentandconditiondontrecurse.xul]
+[test_tmpl_xmlquerywithbindinginbindings.xul]
+[test_tmpl_xmlquerywithbindinginrule.xul]
+[test_tmpl_xmlquerywithdifferentmember.xul]
+[test_tmpl_xmlquerywithinlinedata.xul]
+[test_tmpl_xmlquerywithinlinedatawithmultiplequeries.xul]
+[test_tmpl_xmlquerywithmultiplequeries.xul]
+[test_tmpl_xmlquerywithothertypes.xul]
+[test_tmpl_xmlquerywithsort.xul]
+[test_tmpl_xmlquerywithsortotherfield.xul]
diff --git a/dom/xul/templates/tests/chrome/file_bug330010.rdf b/dom/xul/templates/tests/chrome/file_bug330010.rdf
new file mode 100644
index 000000000..428f1045f
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/file_bug330010.rdf
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:s="urn:croczilla:xulsvg1:">
+ <rdf:Description about="urn:root">
+ <s:shapes>
+ <rdf:Bag>
+ <rdf:li>
+ <rdf:Description />
+ </rdf:li>
+ </rdf:Bag>
+ </s:shapes>
+ </rdf:Description>
+</rdf:RDF>
diff --git a/dom/xul/templates/tests/chrome/templates_shared.js b/dom/xul/templates/tests/chrome/templates_shared.js
new file mode 100644
index 000000000..85c49e52e
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/templates_shared.js
@@ -0,0 +1,488 @@
+/**
+ * This script is used for testing XUL templates. Call test_template within
+ * a load event handler.
+ *
+ * A test should have a root node with the datasources attribute with the
+ * id 'root', and a few global variables defined in the test's XUL file:
+ *
+ * testid: the testid, used when outputting test results
+ * expectedOutput: e4x data containing the expected output. It can optionally
+ * be enclosed in an <output> element as most tests generate
+ * more than one node of output.
+ * isTreeBuilder: true for dont-build-content trees, false otherwise
+ * queryType: 'rdf', 'xml', etc.
+ * needsOpen: true for menu tests where the root menu must be opened before
+ * comparing results
+ * notWorkingYet: true if this test isn't working yet, outputs todo results
+ * notWorkingYetDynamic: true if the dynamic changes portion of the test
+ * isn't working yet, outputs todo results
+ * changes: an array of functions to perform in sequence to test dynamic changes
+ * to the datasource.
+ *
+ * If the <output> element has an unordered attribute set to true, the
+ * children within it must all appear to match, but may appear in any order.
+ * If the unordered attribute is not set, the children must appear in the same
+ * order.
+ *
+ * If the 'changes' array is used, it should be an array of functions. Each
+ * function will be called in order and a comparison of the output will be
+ * performed. This allows changes to be made to the datasource to ensure that
+ * the generated template output has been updated. Within the expected output
+ * XML, the step attribute may be set to a number on an element to indicate
+ * that an element only applies before or after a particular change. If step
+ * is set to a positive number, that element will only exist after that step in
+ * the list of changes made. If step is set to a negative number, that element
+ * will only exist until that step. Steps are numbered starting at 1. For
+ * example:
+ * <label value="Cat"/>
+ * <label step="2" value="Dog"/>
+ * <label step="-5" value="Mouse"/>
+ * The first element will always exist. The second element will only appear
+ * after the second change is made. The third element will only appear until
+ * the fifth change and it will no longer be present at later steps.
+ *
+ * If the anyid attribute is set to true on an element in the expected output,
+ * then the value of the id attribute on that element is not compared for a
+ * 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 = [];
+var expectLoggedMessages = null;
+
+function get_RDF() {
+ try {
+ return Components.classes["@mozilla.org/rdf/rdf-service;1"].
+ getService(Components.interfaces.nsIRDFService);
+ } catch (ex) { }
+}
+
+function get_ContainerUtils()
+{
+ try {
+ return Components.classes["@mozilla.org/rdf/container-utils;1"].
+ getService(Components.interfaces.nsIRDFContainerUtils);
+ } catch(ex) { }
+}
+
+const RDF = get_RDF();
+const ContainerUtils = get_ContainerUtils();
+
+var xmlDoc;
+
+function test_template()
+{
+ var root = document.getElementById("root");
+
+ var ds;
+ if (queryType == "rdf" && RDF) {
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"].
+ getService(Components.interfaces.nsIIOService);
+
+ var src = window.location.href.replace(/test_tmpl.*xul/, "animals.rdf");
+ ds = RDF.GetDataSourceBlocking(src);
+
+ if (expectLoggedMessages) {
+ Components.classes["@mozilla.org/consoleservice;1"].
+ getService(Components.interfaces.nsIConsoleService).reset();
+ }
+
+ if (root.getAttribute("datasources") == "rdf:null")
+ root.setAttribute("datasources", "animals.rdf");
+ }
+ else if (queryType == "xml") {
+ var src = window.location.href.replace(/test_tmpl.*xul/, "animals.xml");
+ xmlDoc = new XMLHttpRequest();
+ xmlDoc.open("get", src, false);
+ xmlDoc.send(null);
+ }
+
+ // open menus if necessary
+ if (needsOpen)
+ root.open = true;
+
+ if (expectLoggedMessages)
+ expectLoggedMessages();
+
+ checkResults(root, 0);
+
+ if (changes.length) {
+ var usedds = ds;
+ // within these chrome tests, RDF datasources won't be modifiable unless
+ // an in-memory datasource is used instead. Call copyRDFDataSource to
+ // copy the datasource.
+ if (queryType == "rdf")
+ 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;
+ if (expectedConsoleMessages.length)
+ compareConsoleMessages();
+ SimpleTest.finish();
+}
+
+function checkResults(root, step)
+{
+ var output = expectedOutput.cloneNode(true);
+ setForCurrentStep(output, step);
+
+ var error;
+ var actualoutput = root;
+ if (isTreeBuilder) {
+ // convert the tree's view data into the equivalent DOM structure
+ // for easier comparison
+ actualoutput = treeViewToDOM(root);
+ var treechildrenElements = [...output.children].filter((e) => e.localName === "treechildren");
+ error = compareOutput(actualoutput, treechildrenElements[0], false);
+ }
+ else {
+ error = compareOutput(actualoutput, output, true);
+ }
+
+ var adjtestid = testid;
+ if (step > 0)
+ adjtestid += " dynamic step " + step;
+
+ var stilltodo = ((step == 0 && notWorkingYet) || (step > 0 && notWorkingYetDynamic));
+ if (stilltodo)
+ todo(false, adjtestid);
+ else
+ ok(!error, adjtestid);
+
+ if ((!stilltodo && error) || debug) {
+ // for debugging, serialize the XML output
+ var serializedXML = "";
+ var rootNodes = actualoutput.childNodes;
+ for (var n = 0; n < rootNodes.length; n++) {
+ var node = rootNodes[n];
+ if (node.localName != "template")
+ serializedXML += ((new XMLSerializer()).serializeToString(node));
+ }
+
+ // remove the XUL namespace declarations to make the output more readable
+ const nsrepl = new RegExp("xmlns=\"" + XUL_NS + "\" ", "g");
+ serializedXML = serializedXML.replace(nsrepl, "");
+ if (debug)
+ dump("-------- " + adjtestid + " " + error + ":\n" + serializedXML + "\n");
+ if (!stilltodo && error)
+ is(serializedXML, "Same", "Error is: " + error);
+ }
+}
+
+/**
+ * Adjust the expected output to acccount for any step attributes.
+ */
+function setForCurrentStep(content, currentStep)
+{
+ var todelete = [];
+ for (var child of content.childNodes) {
+ if (child.nodeType === Node.ELEMENT_NODE) {
+ var stepstr = child.getAttribute("step") || "";
+ var stepsarr = stepstr.split(",");
+ for (var s = 0; s < stepsarr.length; s++) {
+ var step = parseInt(stepsarr[s]);
+ if ((step > 0 && step > currentStep) ||
+ (step < 0 && -step <= currentStep)) {
+ todelete.push(child);
+ }
+ }
+ } else if (child.nodeType === Node.TEXT_NODE) {
+ // Drop empty text nodes.
+ if (child.nodeValue.trim() === "")
+ todelete.push(child);
+ }
+ }
+
+ for (var e of todelete)
+ content.removeChild(e);
+
+ for (var child of content.children) {
+ child.removeAttribute("step");
+ setForCurrentStep(child, currentStep);
+ }
+}
+
+/**
+ * Compares the 'actual' DOM output with the 'expected' output. This function
+ * is called recursively, with isroot true if actual refers to the root of the
+ * template. Returns a null string if they are equal and an error string if
+ * they are not equal. This function is called recursively as it iterates
+ * through each node in the DOM tree.
+ */
+function compareOutput(actual, expected, isroot)
+{
+ if (isroot && expected.localName != "data")
+ return "expected must be a <data> element";
+
+ var t;
+
+ // compare text nodes
+ if (expected.nodeType == Node.TEXT_NODE) {
+ if (actual.nodeValue !== expected.nodeValue.trim())
+ return "Text " + actual.nodeValue + " doesn't match " + expected.nodeValue;
+ return "";
+ }
+
+ if (!isroot) {
+ var anyid = false;
+ // make sure that the tags match
+ if (actual.localName != expected.localName)
+ return "Tag name " + expected.localName + " not found";
+
+ // loop through the attributes in the expected node and compare their
+ // values with the corresponding attribute on the actual node
+
+ var expectedAttrs = expected.attributes;
+ for (var a = 0; a < expectedAttrs.length; a++) {
+ var attr = expectedAttrs[a];
+ var expectval = attr.value;
+ // skip checking the id when anyid="true", however make sure to
+ // ensure that the id is actually present.
+ if (attr.name == "anyid" && expectval == "true") {
+ anyid = true;
+ if (!actual.hasAttribute("id"))
+ return "expected id attribute";
+ }
+ else if (actual.getAttribute(attr.name) != expectval) {
+ return "attribute " + attr.name + " is '" +
+ actual.getAttribute(attr.name) + "' instead of '" + expectval + "'";
+ }
+ }
+
+ // now loop through the actual attributes and make sure that there aren't
+ // any extra attributes that weren't expected
+ var length = actual.attributes.length;
+ for (t = 0; t < length; t++) {
+ var aattr = actual.attributes[t];
+ var expectval = expected.getAttribute(aattr.name);
+ // ignore some attributes that don't matter
+ if (expectval != actual.getAttribute(aattr.name) &&
+ aattr.name != "staticHint" && aattr.name != "xmlns" &&
+ (aattr.name != "id" || !anyid))
+ return "extra attribute " + aattr.name;
+ }
+ }
+
+ // ensure that the node has the right number of children. Subtract one for
+ // the root node to account for the <template> node.
+ length = actual.childNodes.length - (isroot ? 1 : 0);
+ if (length != expected.childNodes.length)
+ return "incorrect child node count of " + actual.localName + " " + length +
+ " expected " + expected.childNodes.length;
+
+ // if <data unordered="true"> is used, then the child nodes may be in any order
+ var unordered = (expected.localName == "data" && expected.getAttribute("unordered") == "true");
+
+ // next, loop over the children and call compareOutput recursively on each one
+ var adj = 0;
+ for (t = 0; t < actual.childNodes.length; t++) {
+ var actualnode = actual.childNodes[t];
+ // skip the <template> element, and add one to the indices when looking
+ // at the later nodes to account for it
+ if (isroot && actualnode.localName == "template") {
+ adj++;
+ }
+ else {
+ var output = "unexpected";
+ if (unordered) {
+ var expectedChildren = expected.childNodes;
+ for (var e = 0; e < expectedChildren.length; e++) {
+ output = compareOutput(actualnode, expectedChildren[e], false);
+ if (!output)
+ break;
+ }
+ }
+ else {
+ output = compareOutput(actualnode, expected.childNodes[t - adj], false);
+ }
+
+ // an error was returned, so return early
+ if (output)
+ return output;
+ }
+ }
+
+ return "";
+}
+
+/*
+ * copy the datasource into an in-memory datasource so that it can be modified
+ */
+function copyRDFDataSource(root, sourceds)
+{
+ var dsourcesArr = [];
+ var composite = root.database;
+ var dsources = composite.GetDataSources();
+ while (dsources.hasMoreElements()) {
+ sourceds = dsources.getNext().QueryInterface(Components.interfaces.nsIRDFDataSource);
+ dsourcesArr.push(sourceds);
+ }
+
+ for (var d = 0; d < dsourcesArr.length; d++)
+ composite.RemoveDataSource(dsourcesArr[d]);
+
+ var newds = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
+ createInstance(Components.interfaces.nsIRDFDataSource);
+
+ var sourcelist = sourceds.GetAllResources();
+ while (sourcelist.hasMoreElements()) {
+ var source = sourcelist.getNext();
+ var props = sourceds.ArcLabelsOut(source);
+ while (props.hasMoreElements()) {
+ var prop = props.getNext();
+ if (prop instanceof Components.interfaces.nsIRDFResource) {
+ var targets = sourceds.GetTargets(source, prop, true);
+ while (targets.hasMoreElements())
+ newds.Assert(source, prop, targets.getNext(), true);
+ }
+ }
+ }
+
+ composite.AddDataSource(newds);
+ root.builder.rebuild();
+
+ return newds;
+}
+
+/**
+ * Converts a tree view (nsITreeView) into the equivalent DOM tree.
+ * Returns the treechildren
+ */
+function treeViewToDOM(tree)
+{
+ var treechildren = document.createElement("treechildren");
+
+ if (tree.view)
+ treeViewToDOMInner(tree.columns, treechildren, tree.view, tree.builder, 0, 0);
+
+ return treechildren;
+}
+
+function treeViewToDOMInner(columns, treechildren, view, builder, start, level)
+{
+ var end = view.rowCount;
+
+ for (var i = start; i < end; i++) {
+ if (view.getLevel(i) < level)
+ return i - 1;
+
+ var id = builder ? builder.getResourceAtIndex(i).Value : "id" + i;
+ var item = document.createElement("treeitem");
+ item.setAttribute("id", id);
+ treechildren.appendChild(item);
+
+ var row = document.createElement("treerow");
+ item.appendChild(row);
+
+ for (var c = 0; c < columns.length; c++) {
+ var cell = document.createElement("treecell");
+ var label = view.getCellText(i, columns[c]);
+ if (label)
+ cell.setAttribute("label", label);
+ row.appendChild(cell);
+ }
+
+ if (view.isContainer(i)) {
+ item.setAttribute("container", "true");
+ item.setAttribute("empty", view.isContainerEmpty(i) ? "true" : "false");
+
+ if (!view.isContainerEmpty(i) && view.isContainerOpen(i)) {
+ item.setAttribute("open", "true");
+
+ var innertreechildren = document.createElement("treechildren");
+ item.appendChild(innertreechildren);
+
+ 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 messages = consoleService.getMessageArray() || [];
+ messages = messages.map(m => m.message);
+ // Copy to avoid modifying expectedConsoleMessages
+ var expect = expectedConsoleMessages.concat();
+ for (var m = 0; m < messages.length; m++) {
+ if (messages[m] == expect[0]) {
+ ok(true, "found message " + expect.shift());
+ }
+ }
+ if (expect.length != 0) {
+ ok(false, "failed to find expected console messages: " + expect);
+ }
+}
+
+function copyToProfile(filename)
+{
+ if (Cc === undefined) {
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+ }
+
+ var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader);
+ loader.loadSubScript("chrome://mochikit/content/chrome-harness.js");
+
+ var file = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+ file.append(filename);
+
+ var parentURI = getResolvedURI(getRootDirectory(window.location.href));
+ if (parentURI.JARFile) {
+ parentURI = extractJarToTmp(parentURI);
+ } else {
+ var fileHandler = Cc["@mozilla.org/network/protocol;1?name=file"].
+ getService(Ci.nsIFileProtocolHandler);
+ parentURI = fileHandler.getFileFromURLSpec(parentURI.spec);
+ }
+
+ parentURI = parentURI.QueryInterface(Ci.nsILocalFile);
+ parentURI.append(filename);
+ try {
+ var retVal = parentURI.copyToFollowingLinks(file.parent, filename);
+ } catch (ex) {
+ //ignore this error as the file could exist already
+ }
+}
diff --git a/dom/xul/templates/tests/chrome/test_bug329335.xul b/dom/xul/templates/tests/chrome/test_bug329335.xul
new file mode 100644
index 000000000..75190c17b
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_bug329335.xul
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:svg="http://www.w3.org/2000/svg">
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <script>
+
+ SimpleTest.waitForExplicitFinish();
+
+ function init()
+ {
+ document.documentElement.appendChild(document.getElementById("svg"));
+ ok(true, "Didn't crash");
+ SimpleTest.finish();
+ }
+
+ window.addEventListener("load", init, false);
+
+ </script>
+
+
+ <svg:svg datasources="" id="svg"/>
+
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_bug330010.xul b/dom/xul/templates/tests/chrome/test_bug330010.xul
new file mode 100644
index 000000000..67cc81931
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_bug330010.xul
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="boom();">
+<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="text/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+function boom()
+{
+ const RDF = Components.classes["@mozilla.org/rdf/rdf-service;1"].
+ getService(Components.interfaces.nsIRDFService);
+ var src = window.location.href.replace(/test_bug330010.xul/, "file_bug330010.rdf");
+
+ var ds = RDF.GetDataSourceBlocking(src);
+
+ var s = document.getElementById("s");
+ s.setAttribute("datasources", "file_bug330010.rdf");
+
+ var x = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "hbox");
+ var generatedShape = s.childNodes[3];
+ generatedShape.appendChild(x);
+ document.documentElement.removeChild(document.getElementById("s"));
+ ok(true, "Didn't crash");
+ SimpleTest.finish();
+}
+
+]]>
+</script>
+
+ <html:div datasources="rdf:null" ref="urn:root" flex="1" id="s">
+ <template>
+ <rule>
+ <conditions>
+ <content uri="?root"/>
+ <triple subject="?root"
+ predicate="urn:croczilla:xulsvg1:shapes"
+ object="?shapes"/>
+ <member container="?shapes" child="?shape" id="m"/>
+ </conditions>
+ <action>
+ <hbox id="p" uri="?shape" />
+ </action>
+ </rule>
+ </template>
+ </html:div>
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_bug397148.xul b/dom/xul/templates/tests/chrome/test_bug397148.xul
new file mode 100644
index 000000000..0f15cfa29
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_bug397148.xul
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" onload="boom();">
+<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function boom() {
+ ok(true, "Didn't crash");
+ SimpleTest.finish();
+}
+</script>
+
+
+<html:span datasources="0" />
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_bug441785.xul b/dom/xul/templates/tests/chrome/test_bug441785.xul
new file mode 100644
index 000000000..7be6df198
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_bug441785.xul
@@ -0,0 +1,148 @@
+<?xml version="1.0" ?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <tree flex="20" id="t" ref="urn:data:row" datasources="rdf:null" seltype="single">
+ <treecols>
+ <treecol flex="1" id="id" label="id" sort="rdf:http://dummy/rdf#id" />
+ <splitter class="tree-splitter"/>
+ <treecol flex="1" id="title" label="title" sort="rdf:http://dummy/rdf#title" sortActive="true" sortDirection="ascending" /><splitter class="tree-splitter"/>
+ </treecols>
+ <template>
+ <treechildren>
+ <treeitem uri="rdf:*" seltype="single">
+ <treerow >
+ <treecell label="rdf:http://dummy/rdf#id"/>
+ <treecell label="rdf:http://dummy/rdf#title"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </template>
+ </tree>
+ <tree flex="20" id="tc" ref="urn:data:row" datasources="rdf:null" seltype="single" flags="dont-build-content">
+ <treecols>
+ <treecol flex="1" id="idc" label="id" sort="rdf:http://dummy/rdf#id" />
+ <splitter class="tree-splitter"/>
+ <treecol flex="1" id="titlec" label="title" sort="rdf:http://dummy/rdf#title" sortActive="true" sortDirection="ascending" /><splitter class="tree-splitter"/>
+ </treecols>
+ <template>
+ <treechildren>
+ <treeitem uri="rdf:*" seltype="single">
+ <treerow >
+ <treecell label="rdf:http://dummy/rdf#id"/>
+ <treecell label="rdf:http://dummy/rdf#title"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </template>
+ </tree>
+
+<body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script type="application/x-javascript">
+<![CDATA[
+
+var buildCount = 0;
+
+SimpleTest.waitForExplicitFinish();
+
+var TemplateBuilderListener = {
+ willRebuild: function(aBuilder) {
+ },
+
+ didRebuild: function(aBuilder) {
+ ++buildCount;
+ var remove = false;
+ if (buildCount == 2) {
+ remove =true;
+ setTimeout(nextDataSource, 0);
+ } else if (buildCount == 4) {
+ remove = true;
+ setTimeout(continueTest, 0);
+ }
+ if (remove) {
+ var tree = document.getElementById('t');
+ var treec = document.getElementById('tc');
+ tree.builder.removeListener(TemplateBuilderListener);
+ treec.builder.removeListener(TemplateBuilderListener);
+ }
+ },
+
+ QueryInterface: function (aIID)
+ {
+ if (!aIID.equals(Components.interfaces.nsIXULBuilderListener) &&
+ !aIID.equals(Components.interfaces.nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+};
+
+function runTest() {
+ var tree = document.getElementById('t');
+ var treec = document.getElementById('tc');
+
+ try {
+ var rdfService = Components.classes["@mozilla.org/rdf/rdf-service;1"].
+ getService(Components.interfaces.nsIRDFService);
+
+ var s1 = window.location.href.replace(/test_bug441785.xul/, "bug441785-1.rdf");
+ var ds1 = rdfService.GetDataSourceBlocking(s1);
+
+ var s2 = window.location.href.replace(/test_bug441785.xul/, "bug441785-2.rdf");
+ var ds2 = rdfService.GetDataSourceBlocking(s2);
+ } catch (ex) { }
+
+ tree.builder.addListener(TemplateBuilderListener);
+ treec.builder.addListener(TemplateBuilderListener);
+ tree.setAttribute('datasources', 'bug441785-1.rdf');
+ treec.setAttribute('datasources', 'bug441785-1.rdf');
+}
+
+var oldtreefirstrow, oldtreecfirstrow;
+
+function nextDataSource()
+{
+ var tree = document.getElementById('t');
+ var treec = document.getElementById('tc');
+ tree.treeBoxObject.scrollToRow(10);
+ treec.treeBoxObject.scrollToRow(10);
+
+ is(tree.treeBoxObject.getFirstVisibleRow(), 10, "first tree row count datasource 1");
+ is(treec.treeBoxObject.getFirstVisibleRow(), 10, "second tree row count datasource 1");
+
+ tree.builder.addListener(TemplateBuilderListener);
+ treec.builder.addListener(TemplateBuilderListener);
+ tree.setAttribute('datasources', 'bug441785-2.rdf');
+ treec.setAttribute('datasources', 'bug441785-2.rdf');
+}
+
+function continueTest() {
+ var tree = document.getElementById('t');
+ var treec = document.getElementById('tc');
+
+ is(tree.treeBoxObject.getFirstVisibleRow(), 0, "first tree row count datasource 2");
+ is(treec.treeBoxObject.getFirstVisibleRow(), 0, "second tree row count datasource 2");
+
+ try {
+ window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils)
+ .garbageCollect();
+ }
+ catch (e) { }
+
+ // Hit the bug, crash
+ // (not exactly the same kind of crash as 441785, but from the same cause)
+ tree.parentNode.removeChild(tree);
+ treec.parentNode.removeChild(treec);
+
+ SimpleTest.finish();
+}
+
+window.addEventListener("load", runTest, false);
+
+]]>
+</script>
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_bug476634.xul b/dom/xul/templates/tests/chrome/test_bug476634.xul
new file mode 100644
index 000000000..cee01d41a
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_bug476634.xul
@@ -0,0 +1,76 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=476634
+-->
+<window title="Mozilla Bug 476634" onload="startup()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=304188">Mozilla Bug 476634</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+<template id="test-template">
+ <query>SELECT id,value FROM test</query>
+ <action>
+ <label uri="?" id="?id" value="?value"/>
+ </action>
+</template>
+<vbox id="results-list" datasources="rdf:null" querytype="storage" ref="*"
+ template="test-template"/>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+function startup() {
+ var ss = Components.classes["@mozilla.org/storage/service;1"]
+ .getService(Components.interfaces.mozIStorageService);
+ var db = ss.openSpecialDatabase("memory");
+
+ db.createTable("test", "id TEXT, value INTEGER");
+ var stmt = db.createStatement("INSERT INTO test (id, value) VALUES (?,?)");
+ stmt.bindByIndex(0, "test1");
+ stmt.bindByIndex(1, 0);
+ stmt.execute();
+ stmt.bindByIndex(0, "test2");
+ stmt.bindByIndex(1, 2147483647);
+ stmt.execute();
+ stmt.bindByIndex(0, "test3");
+ stmt.bindByIndex(1, -2147483648);
+ stmt.execute();
+ stmt.bindByIndex(0, "test4");
+ stmt.bindByIndex(1, 0);
+ stmt.execute();
+ stmt.bindByIndex(0, "test5");
+ stmt.bindByIndex(1, 3147483647);
+ stmt.execute();
+ stmt.bindByIndex(0, "test6");
+ stmt.bindByIndex(1, -3147483648);
+ stmt.execute();
+ stmt.finalize();
+
+ var list = document.getElementById("results-list");
+ list.builder.datasource = db;
+
+ is(list.childNodes.length, 6, "Should be 6 generated elements");
+ is(list.childNodes[0].value, "0", "Should have seen the correct value");
+ is(list.childNodes[1].value, "2147483647", "Should have seen the correct value");
+ is(list.childNodes[2].value, "-2147483648", "Should have seen the correct value");
+ is(list.childNodes[3].value, "0", "Should have seen the correct value");
+ is(list.childNodes[4].value, "3147483647", "Should have seen the correct value");
+ is(list.childNodes[5].value, "-3147483648", "Should have seen the correct value");
+}
+]]>
+</script>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_sortservice.xul b/dom/xul/templates/tests/chrome/test_sortservice.xul
new file mode 100644
index 000000000..af1de3f0e
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_sortservice.xul
@@ -0,0 +1,70 @@
+<?xml version="1.0" ?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<vbox id="box"/>
+
+<body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script type="application/x-javascript">
+<![CDATA[
+
+var tests = [
+ [["One", "Two", "Three", "Four"], "", "Four One Three Two"],
+ [["One", "Two", "Three", "Four"], "integer", "Four One Three Two"],
+ [["One", "Two", "Three", "Four"], "descending", "Two Three One Four"],
+ [["One", "Two", "Three", "Four"], "descending integer", "Two Three One Four"],
+ [["One", "Two", "Three", "Four"], "integer cat descending", "Two Three One Four"],
+ [["1", "13", "2", "7", "12", "240", "2", "170", "222", "98"], "", "1 12 13 170 2 2 222 240 7 98"],
+ [["1", "13", "2", "7", "12", "240", "2", "170", "222", "98"], "integer", "1 2 2 7 12 13 98 170 222 240"],
+ [["1", "13", "2", "7", "12", "240", "2", "170", "222", "98"], "ascending integer", "1 2 2 7 12 13 98 170 222 240"],
+ [["1", "13", "2", "7", "12", "240", "2", "170", "222", "98"], "integer descending", "240 222 170 98 13 12 7 2 2 1"],
+ [["Cat", "cat", "Candy", "candy"], "comparecase", "Candy Cat candy cat"],
+ [["1", "102", "22", "One", "40", "Two"], "integer", "1 22 40 102 One Two"],
+];
+
+SimpleTest.waitForExplicitFinish();
+
+function doTests()
+{
+ var box = document.getElementById("box");
+
+ const sortService = Components.classes["@mozilla.org/xul/xul-sort-service;1"].
+ getService(Components.interfaces.nsIXULSortService);
+
+ for (let t = 0; t < tests.length; t++) {
+ var test = tests[t];
+
+ for (let e = 0; e < test[0].length; e++) {
+ var label = document.createElement("label");
+ label.setAttribute("value", test[0][e]);
+ box.appendChild(label);
+ }
+
+ sortService.sort(box, "value", test[1]);
+
+ var actual = "";
+ for (let e = 0; e < box.childNodes.length; e++) {
+ if (actual)
+ actual += " ";
+ actual += box.childNodes[e].getAttribute("value");
+ }
+ is(actual, test[2], "sorted step " + (t + 1));
+
+ while(box.hasChildNodes())
+ box.removeChild(box.firstChild);
+ box.removeAttribute("sortDirection");
+ }
+
+ SimpleTest.finish();
+}
+
+window.addEventListener("load", doTests, false);
+
+]]>
+</script>
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_bindingsextendedsyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_bindingsextendedsyntax.xul
new file mode 100644
index 000000000..42e9a1633
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_bindingsextendedsyntax.xul
@@ -0,0 +1,73 @@
+<?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"?>
+
+<!--
+ bindings - extended syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="-2" id="http://www.some-fictitious-zoo.com/humans/sarah" value="Sarah "/>
+ <label step="2" id="http://www.some-fictitious-zoo.com/humans/sarah" value="Sarah Yarmouth"/>
+ <label step="-1" id="http://www.some-fictitious-zoo.com/humans/robert" value="Robert Sanderson"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/humans/robert" value="Robert "/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="bindings - extended syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ targetds.Unassert(RDF.GetResource(ZOO_NS + 'humans/robert'),
+ RDF.GetResource(ZOO_NS + 'rdf#lastName'),
+ RDF.GetLiteral('Sanderson'), true);
+ },
+ // step 2
+ function(targetds, root) {
+ targetds.Assert(RDF.GetResource(ZOO_NS + 'humans/sarah'),
+ RDF.GetResource(ZOO_NS + 'rdf#lastName'),
+ RDF.GetLiteral('Yarmouth'), true);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/humans">
+<template id="template">
+<rule>
+<conditions>
+<content uri="?uri"/>
+<member container="?uri" child="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</conditions>
+<bindings>
+<binding subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#lastName" object="?lastname"/>
+</bindings>
+<action>
+<label uri="?child" value="?name ?lastname"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_bindingsmultiple.xul b/dom/xul/templates/tests/chrome/test_tmpl_bindingsmultiple.xul
new file mode 100644
index 000000000..2af4f89aa
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_bindingsmultiple.xul
@@ -0,0 +1,80 @@
+<?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"?>
+
+<!--
+ bindings - multiple
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <button id="http://www.some-fictitious-zoo.com/arachnids" container="true" empty="false" label="Arachnids "/>
+ <button step="-2" id="http://www.some-fictitious-zoo.com/birds" container="true" empty="false" label="Birds Sarah "/>
+ <button step="2" id="http://www.some-fictitious-zoo.com/birds" container="true" empty="false" label="Birds Sarah Yarmouth"/>
+ <button step="-1" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true" label="Crustaceans Robert Sanderson"/>
+ <button step="1" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true" label="Crustaceans Robert "/>
+ <button id="http://www.some-fictitious-zoo.com/fish" container="true" empty="false" label="Fish "/>
+ <button id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false" label="Mammals "/>
+ <button step="-1" id="http://www.some-fictitious-zoo.com/reptiles" container="true" empty="false" label="Reptiles Robert Sanderson"/>
+ <button step="1" id="http://www.some-fictitious-zoo.com/reptiles" container="true" empty="false" label="Reptiles Robert "/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="bindings - multiple";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ targetds.Unassert(RDF.GetResource(ZOO_NS + 'humans/robert'),
+ RDF.GetResource(ZOO_NS + 'rdf#lastName'),
+ RDF.GetLiteral('Sanderson'), true);
+ },
+ // step 2
+ function(targetds, root) {
+ targetds.Assert(RDF.GetResource(ZOO_NS + 'humans/sarah'),
+ RDF.GetResource(ZOO_NS + 'rdf#lastName'),
+ RDF.GetLiteral('Yarmouth'), true);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/all-animals">
+<template>
+<query>
+<content uri="?uri"/>
+<member container="?uri" child="?child"/>
+</query>
+<rule>
+<bindings>
+<binding subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#keeper" object="?keeper"/>
+<binding subject="?keeper" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?keepername"/>
+<binding subject="?keeper" predicate="http://www.some-fictitious-zoo.com/rdf#lastName" object="?lastname"/>
+<binding subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</bindings>
+<action>
+<button uri="?child" label="?name ?keepername ?lastname"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_bindingsquerysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_bindingsquerysyntax.xul
new file mode 100644
index 000000000..d8b6110be
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_bindingsquerysyntax.xul
@@ -0,0 +1,73 @@
+<?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"?>
+
+<!--
+ bindings - query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="-2" id="http://www.some-fictitious-zoo.com/humans/sarah" value="First Name: Sarah Last Name: "/>
+ <label step="2" id="http://www.some-fictitious-zoo.com/humans/sarah" value="First Name: Sarah Last Name: Yarmouth"/>
+ <label step="-1" id="http://www.some-fictitious-zoo.com/humans/robert" value="First Name: Robert Last Name: Sanderson"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/humans/robert" value="First Name: Robert Last Name: "/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="bindings - query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ targetds.Unassert(RDF.GetResource(ZOO_NS + 'humans/robert'),
+ RDF.GetResource(ZOO_NS + 'rdf#lastName'),
+ RDF.GetLiteral('Sanderson'), true);
+ },
+ // step 2
+ function(targetds, root) {
+ targetds.Assert(RDF.GetResource(ZOO_NS + 'humans/sarah'),
+ RDF.GetResource(ZOO_NS + 'rdf#lastName'),
+ RDF.GetLiteral('Yarmouth'), true);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/humans">
+<template id="template">
+<query>
+<content uri="?uri"/>
+<member container="?uri" child="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<rule id="rule">
+<bindings id="bindings">
+<binding subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#lastName" object="?lastname"/>
+</bindings>
+<action>
+<label uri="?child" value="First Name: ?name Last Name: ?lastname"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_bindingsreversed.xul b/dom/xul/templates/tests/chrome/test_tmpl_bindingsreversed.xul
new file mode 100644
index 000000000..a7bd190a7
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_bindingsreversed.xul
@@ -0,0 +1,58 @@
+<?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"?>
+
+<!--
+ bindings - reversed
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/humans/sarah" value="First Name: Sarah Last Name: "/>
+ <label id="http://www.some-fictitious-zoo.com/humans/robert" value="First Name: Robert Last Name: "/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="bindings - reversed";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/humans">
+<template id="template">
+<query>
+<content uri="?uri"/>
+<member container="?uri" child="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<rule id="rule">
+<bindings id="bindings">
+<binding subject="?lastname" predicate="http://www.some-fictitious-zoo.com/rdf#lastName" object="?child"/>
+</bindings>
+<action>
+<label uri="?child" value="First Name: ?name Last Name: ?lastname"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_bindingssameastriple.xul b/dom/xul/templates/tests/chrome/test_tmpl_bindingssameastriple.xul
new file mode 100644
index 000000000..514979d59
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_bindingssameastriple.xul
@@ -0,0 +1,58 @@
+<?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"?>
+
+<!--
+ bindings - same as triple
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/humans/sarah" value="First Name: Sarah Last Name: "/>
+ <label id="http://www.some-fictitious-zoo.com/humans/robert" value="First Name: Robert Last Name: "/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="bindings - same as triple";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/humans">
+<template id="template">
+<query>
+<content uri="?uri"/>
+<member container="?uri" child="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<rule id="rule">
+<bindings id="bindings">
+<binding subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</bindings>
+<action>
+<label uri="?child" value="First Name: ?name Last Name: ?lastname"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_containerandmembervariablechanged.xul b/dom/xul/templates/tests/chrome/test_tmpl_containerandmembervariablechanged.xul
new file mode 100644
index 000000000..7b90d64b1
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_containerandmembervariablechanged.xul
@@ -0,0 +1,88 @@
+<?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"?>
+
+<!--
+ container and member variable changed
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <button step="3" id="http://www.some-fictitious-zoo.com/birds/wren" label="http://www.some-fictitious-zoo.com/birds http://www.some-fictitious-zoo.com/birds/wren"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/emu" label="http://www.some-fictitious-zoo.com/birds http://www.some-fictitious-zoo.com/birds/emu"/>
+ <button step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl" label="http://www.some-fictitious-zoo.com/birds http://www.some-fictitious-zoo.com/birds/barnowl"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/raven" label="http://www.some-fictitious-zoo.com/birds http://www.some-fictitious-zoo.com/birds/raven"/>
+ <button step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx" label="http://www.some-fictitious-zoo.com/birds http://www.some-fictitious-zoo.com/birds/archaeopteryx"/>
+ <button step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" label="http://www.some-fictitious-zoo.com/birds http://www.some-fictitious-zoo.com/birds/emperorpenguin"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="container and member variable changed";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template container="?parent" member="?child">
+<rule>
+<button uri="?" label="?parent ?child"/>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_containervariablechanged.xul b/dom/xul/templates/tests/chrome/test_tmpl_containervariablechanged.xul
new file mode 100644
index 000000000..f9100047f
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_containervariablechanged.xul
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ container variable changed
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <button id="http://www.some-fictitious-zoo.com/birds/emu" label="http://www.some-fictitious-zoo.com/birds "/>
+ <button id="http://www.some-fictitious-zoo.com/birds/barnowl" label="http://www.some-fictitious-zoo.com/birds "/>
+ <button id="http://www.some-fictitious-zoo.com/birds/raven" label="http://www.some-fictitious-zoo.com/birds "/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="container variable changed";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template container="?parent">
+<rule>
+<button uri="?" label="?parent ?child"/>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_containmentattribute.xul b/dom/xul/templates/tests/chrome/test_tmpl_containmentattribute.xul
new file mode 100644
index 000000000..909a05e34
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_containmentattribute.xul
@@ -0,0 +1,71 @@
+<?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"?>
+
+<!--
+ containment attribute
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output" unordered="true">
+ <checkbox step="-1" id="http://www.some-fictitious-zoo.com/arachnids/tarantula" label="Tarantula"/>
+ <checkbox step="2" id="http://www.some-fictitious-zoo.com/mammals/lion" label="Lion"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/reptiles/anaconda" label="Anaconda"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/reptiles/chameleon" label="Chameleon"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals/africanelephant" label="African Elephant"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="containment attribute";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ targetds.Unassert(RDF.GetResource(ZOO_NS + 'humans/robert'),
+ RDF.GetResource(ZOO_NS + 'rdf#favoriteAnimal'),
+ RDF.GetResource(ZOO_NS + 'arachnids/tarantula'), true);
+ },
+ // step 2
+ function(targetds, root) {
+ targetds.Assert(RDF.GetResource(ZOO_NS + 'humans/robert'),
+ RDF.GetResource(ZOO_NS + 'rdf#favoriteAnimal'),
+ RDF.GetResource(ZOO_NS + 'mammals/lion'), true);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/humans/robert" containment="http://www.some-fictitious-zoo.com/rdf#favoriteAnimal">
+<template id="template">
+<rule id="rule">
+<conditions>
+<content uri="?uri"/>
+<member container="?uri" child="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</conditions>
+<action>
+<checkbox uri="?child" label="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_defaultcontainervariableisuri.xul b/dom/xul/templates/tests/chrome/test_tmpl_defaultcontainervariableisuri.xul
new file mode 100644
index 000000000..933c51bc0
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_defaultcontainervariableisuri.xul
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ default container variable is ?uri
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <button id="http://www.some-fictitious-zoo.com/birds/emu" label="http://www.some-fictitious-zoo.com/birds"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/barnowl" label="http://www.some-fictitious-zoo.com/birds"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/raven" label="http://www.some-fictitious-zoo.com/birds"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="default container variable is ?uri";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template>
+<rule>
+<button uri="?uri" label="?uri"/>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_errors.xul b/dom/xul/templates/tests/chrome/test_tmpl_errors.xul
new file mode 100644
index 000000000..f3b58502d
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_errors.xul
@@ -0,0 +1,280 @@
+<?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/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 message = consoleService.getMessageArray()[0].message;
+ is(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: template => template.insertBefore(document.createElement("queryset"), template.lastChild),
+ error: "Error parsing template: unexpected <queryset> element",
+ post: queryset => queryset.parentNode.removeChild(queryset)
+},
+
+// no member variable found
+{
+ pre: template => $("action").firstChild.removeAttribute("uri"),
+ error: "Error parsing template: no member variable found. Action body should have an element with uri attribute",
+ post: () => $("action").firstChild.setAttribute("uri", "?child")
+},
+
+// bad binding subject
+{
+ pre: template => $("binding").removeAttribute("subject"),
+ error: "Error parsing template: <binding> requires a variable for its subject attribute",
+ post: () => $("binding").setAttribute("subject", "?child"),
+},
+
+// bad binding predicate
+{
+ pre: template => $("binding").removeAttribute("predicate"),
+ error: "Error parsing template: <binding> element is missing a predicate attribute",
+ post: () => $("binding").setAttribute("predicate", "http://www.some-fictitious-zoo.com/rdf#name"),
+},
+
+// bad binding object
+{
+ pre: template => $("binding").setAttribute("object", "blah"),
+ error: "Error parsing template: <binding> requires a variable for its object attribute",
+ post: () => $("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: template => $("member").setAttribute("container", "blah"),
+ error: "Error parsing template: <member> requires a variable for its container attribute",
+ post: () => $("member").setAttribute("container", "?uri"),
+},
+
+// bad member child
+{
+ pre: template => $("member").setAttribute("child", "blah"),
+ error: "Error parsing template: <member> requires a variable for its child attribute",
+ post: () => $("member").setAttribute("child", "?child"),
+},
+
+// bad triple subject
+{
+ pre: template => $("triple").removeAttribute("subject"),
+ error: "Error parsing template: <triple> requires a variable for its subject attribute",
+ post: () => $("triple").setAttribute("subject", "?child"),
+},
+
+// missing triple predicate
+{
+ pre: template => $("triple").removeAttribute("predicate"),
+ error: "Error parsing template: <triple> should have a non-variable value as a predicate",
+ post: () => $("triple").setAttribute("predicate", "http://www.some-fictitious-zoo.com/rdf#name"),
+},
+
+// bad triple predicate
+{
+ pre: template => $("triple").setAttribute("predicate", "?predicate"),
+ error: "Error parsing template: <triple> should have a non-variable value as a predicate",
+ post: () => $("triple").setAttribute("predicate", "http://www.some-fictitious-zoo.com/rdf#name"),
+},
+
+// bad triple object
+{
+ pre: template => $("triple").removeAttribute("object"),
+ error: "Error parsing template: <triple> requires a variable for its object attribute",
+ post: () => $("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: content => content.parentNode.insertBefore(content, content.parentNode.firstChild),
+},
+
+// member container variable not bound
+{
+ pre: template => $("member").removeAttribute("container"),
+ error: "Error parsing template: neither container or child variables of <member> has a value",
+ post: () => $("member").setAttribute("container", "?uri"),
+},
+
+// neither triple subject or object variable are bound
+{
+ pre: template => $("triple").setAttribute("subject", "?blah"),
+ error: "Error parsing template: neither subject or object variables of <triple> has a value",
+ post: () => $("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]");
+
+ function waitForDatasource() {
+ // wait for the datasource to be available before continuing the test
+ if (root.builder.datasource instanceof XMLDocument)
+ runTest();
+ else
+ setTimeout(waitForDatasource, 100);
+ }
+
+ setTimeout(waitForDatasource, 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>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntax.xul
new file mode 100644
index 000000000..1bd31525e
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntax.xul
@@ -0,0 +1,95 @@
+<?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"?>
+
+<!--
+ extended syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="3" id="http://www.some-fictitious-zoo.com/birds/wren" value="Wren"/>
+ <label id="http://www.some-fictitious-zoo.com/birds/emu" value="Emu"/>
+ <label step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl" value="Barn Owl"/>
+ <label id="http://www.some-fictitious-zoo.com/birds/raven" value="Raven"/>
+ <label step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx" value="Archaeopteryx"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" value="Emperor Penguin"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="extended syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template id="template">
+<rule id="rule">
+<conditions id="conditions">
+<content uri="?uri"/>
+<member container="?uri" child="?animal"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxemptyconditions.xul b/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxemptyconditions.xul
new file mode 100644
index 000000000..8e0e37c3a
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxemptyconditions.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"?>
+
+<!--
+ extended syntax - empty conditions
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output"/>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="extended syntax - empty conditions";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template id="template">
+<rule id="rule">
+<conditions id="conditions"/>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxotherrefvariable.xul b/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxotherrefvariable.xul
new file mode 100644
index 000000000..6895034f2
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxotherrefvariable.xul
@@ -0,0 +1,95 @@
+<?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"?>
+
+<!--
+ extended syntax - other ref variable
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="3" id="http://www.some-fictitious-zoo.com/birds/wren" value="Wren"/>
+ <label id="http://www.some-fictitious-zoo.com/birds/emu" value="Emu"/>
+ <label step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl" value="Barn Owl"/>
+ <label id="http://www.some-fictitious-zoo.com/birds/raven" value="Raven"/>
+ <label step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx" value="Archaeopteryx"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" value="Emperor Penguin"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="extended syntax - other ref variable";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template id="template">
+<rule id="rule">
+<conditions id="conditions">
+<content uri="?start"/>
+<member container="?start" child="?animal"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxremoveunmatched.xul b/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxremoveunmatched.xul
new file mode 100644
index 000000000..b2ef77d9e
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxremoveunmatched.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ extended syntax, remove unmatched
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/humans/robert" value="Robert Sanderson"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="extended syntax, remove unmatched";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/humans">
+<template id="template">
+<rule>
+<conditions>
+<content uri="?uri"/>
+<member container="?uri" child="?human"/>
+<triple subject="?human" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+<triple subject="?human" predicate="http://www.some-fictitious-zoo.com/rdf#lastName" object="?lastname"/>
+</conditions>
+<action id="action">
+<label uri="?human" value="?name ?lastname"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxsimplevariablesubstitution.xul b/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxsimplevariablesubstitution.xul
new file mode 100644
index 000000000..d38bd96c0
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxsimplevariablesubstitution.xul
@@ -0,0 +1,54 @@
+<?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"?>
+
+<!--
+ extended syntax - simple variable substitution
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value=""/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="extended syntax - simple variable substitution";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/arachnids">
+<template id="template">
+<rule id="rule">
+<conditions id="conditions">
+<content uri="?start"/>
+<member container="?start" child="?animal"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxtworulesrecurse.xul b/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxtworulesrecurse.xul
new file mode 100644
index 000000000..d0e4faadf
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxtworulesrecurse.xul
@@ -0,0 +1,81 @@
+<?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"?>
+
+<!--
+ extended syntax - two rules recurse
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <vbox id="http://www.some-fictitious-zoo.com/humans/sarah">
+ <label value="The favorite animals of Sarah"/>
+ <label id="http://www.some-fictitious-zoo.com/birds/emu" class="indent" value="Emu which belongs to the class Birds"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" class="indent" value="Polar Bear which belongs to the class Mammals"/>
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" class="indent" value="Tarantula which belongs to the class Arachnids"/>
+ </vbox>
+ <vbox id="http://www.some-fictitious-zoo.com/humans/robert">
+ <label value="The favorite animals of Robert"/>
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" class="indent" value="Tarantula which belongs to the class Arachnids"/>
+ <label id="http://www.some-fictitious-zoo.com/reptiles/anaconda" class="indent" value="Anaconda which belongs to the class Reptiles"/>
+ <label id="http://www.some-fictitious-zoo.com/reptiles/chameleon" class="indent" value="Chameleon which belongs to the class Reptiles"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" class="indent" value="African Elephant which belongs to the class Mammals"/>
+ </vbox>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="extended syntax - two rules recurse";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/humans">
+<template>
+<rule>
+<conditions>
+<content uri="?uri"/>
+<member container="?uri" child="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</conditions>
+<action>
+<vbox uri="?child">
+<label value="The favorite animals of ?name"/>
+</vbox>
+</action>
+</rule>
+<rule>
+<conditions>
+<content uri="?uri"/>
+<triple subject="?uri" predicate="http://www.some-fictitious-zoo.com/rdf#favoriteAnimal" object="?child"/>
+<member container="?animalClass" child="?child"/>
+<triple subject="?animalClass" predicate="http://www.w3.org/1999/02/22-rdf-syntax-ns#type" object="http://www.some-fictitious-zoo.com/rdf#Class"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+<triple subject="?animalClass" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?animalClassName"/>
+</conditions>
+<action>
+<label uri="?child" class="indent" value="?name which belongs to the class ?animalClassName"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxusinganinterveningcontainer.xul b/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxusinganinterveningcontainer.xul
new file mode 100644
index 000000000..2e9ce8a2f
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_extendedsyntaxusinganinterveningcontainer.xul
@@ -0,0 +1,60 @@
+<?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"?>
+
+<!--
+ extended syntax using an intervening container
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <groupbox>
+ <label id="http://www.some-fictitious-zoo.com/birds/emu" value="Emu"/>
+ <label id="http://www.some-fictitious-zoo.com/birds/barnowl" value="Barn Owl"/>
+ <label id="http://www.some-fictitious-zoo.com/birds/raven" value="Raven"/>
+ </groupbox>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="extended syntax using an intervening container";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template id="template">
+<rule id="rule">
+<conditions id="conditions">
+<content uri="?start"/>
+<member container="?start" child="?animal"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</conditions>
+<action id="action">
+<groupbox>
+<label uri="?animal" value="?name"/>
+</groupbox>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_extendedvariablesubstitution.xul b/dom/xul/templates/tests/chrome/test_tmpl_extendedvariablesubstitution.xul
new file mode 100644
index 000000000..deee822dd
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_extendedvariablesubstitution.xul
@@ -0,0 +1,54 @@
+<?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"?>
+
+<!--
+ extended variable substitution
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Tarantula? - TarantulaTarantula^ Test"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="extended variable substitution";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/arachnids">
+<template id="template">
+<rule id="rule">
+<conditions id="conditions">
+<content uri="?start"/>
+<member container="?start" child="?animal"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name^?? ?name?name - ?name^?name^^ Test"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_gridelement.xul b/dom/xul/templates/tests/chrome/test_tmpl_gridelement.xul
new file mode 100644
index 000000000..12575c6b8
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_gridelement.xul
@@ -0,0 +1,131 @@
+<?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"?>
+
+<!--
+ grid element
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <columns>
+ <column/>
+ <column/>
+ </columns>
+ <rows>
+ <label step="3" id="http://www.some-fictitious-zoo.com/birds/wren" value="The coolest animal is: Wren"/>
+ <row id="http://www.some-fictitious-zoo.com/birds/emu">
+ <label value="Emu"/>
+ <label value="Birds"/>
+ </row>
+ <label step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl" value="The coolest animal is: Barn Owl"/>
+ <row id="http://www.some-fictitious-zoo.com/birds/raven">
+ <label value="Raven"/>
+ <label value="Birds"/>
+ </row>
+ <row step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx">
+ <label value="Archaeopteryx"/>
+ <label value="Birds"/>
+ </row>
+ <label step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" value="The coolest animal is: Emperor Penguin"/>
+ </rows>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="grid element";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<grid xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<columns>
+<column/>
+<column/>
+</columns>
+<template>
+<query>
+<content uri="?uri"/>
+<member container="?uri" child="?child"/>
+<triple subject="?uri" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?classname"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<rule>
+<conditions>
+<where subject="?name" rel="equals" value="Raven" negate="true"/>
+<where subject="?name" rel="contains" value="n"/>
+</conditions>
+<action>
+<rows>
+<label uri="?child" value="The coolest animal is: ?name"/>
+</rows>
+</action>
+</rule>
+<rule>
+<action>
+<rows>
+<row uri="?child">
+<label value="?name"/>
+<label value="?classname"/>
+</row>
+</rows>
+</action>
+</rule>
+</template>
+</grid>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_htmlelementextendedsyntaxwithbinding.xul b/dom/xul/templates/tests/chrome/test_tmpl_htmlelementextendedsyntaxwithbinding.xul
new file mode 100644
index 000000000..a9a14e00a
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_htmlelementextendedsyntaxwithbinding.xul
@@ -0,0 +1,114 @@
+<?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"?>
+
+<!--
+ html element - extended syntax with binding
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output" xmlns:html="http://www.w3.org/1999/xhtml">
+ <html:div id="http://www.some-fictitious-zoo.com/mammals/lion" title="Lion">
+ <html:em step="-2">4</html:em>
+ <html:em step="2">9</html:em>
+ </html:div>
+ <html:div id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" title="HIPPOPOTAMUS">
+ <html:em>2</html:em>
+ </html:div>
+ <html:p id="http://www.some-fictitious-zoo.com/mammals/africanelephant">
+ African Elephant
+ <html:span title="14"/>
+ </html:p>
+ <html:p step="4" id="http://www.some-fictitious-zoo.com/mammals/chimpanzee">
+ Chimpanzee
+ <html:span step="-5"/>
+ <html:span step="5" title="3"/>
+ </html:p>
+ <html:div id="http://www.some-fictitious-zoo.com/mammals/llama" title="LLAMA">
+ <html:em>5</html:em>
+ </html:div>
+ <html:div id="http://www.some-fictitious-zoo.com/mammals/polarbear" title="Polar Bear">
+ <html:em step="-1">20</html:em>
+ <html:em step="1">5</html:em>
+ </html:div>
+ <html:div id="http://www.some-fictitious-zoo.com/mammals/aardvark" title="aardvark">
+ <html:em>2</html:em>
+ </html:div>
+ <html:p step="-3" id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo">
+ Nine-banded Armadillo
+ <html:span title="1"/>
+ </html:p>
+ <html:div id="http://www.some-fictitious-zoo.com/mammals/gorilla" title="Gorilla">
+ <html:em>7</html:em>
+ </html:div>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="html element - extended syntax with binding";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'mammals/polarbear');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#specimens');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'mammals/polarbear'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ oldval, RDF.GetLiteral('5'));
+ },
+ // step 2
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'mammals/lion');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#specimens');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'mammals/lion'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ oldval, RDF.GetLiteral('9'));
+ },
+ // step 3
+ function(targetds, root) {
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'mammals'));
+ var removednode = container.RemoveElementAt('7', true);
+ targetds.Unassert(removednode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+ RDF.GetLiteral('Nine-banded Armadillo'), true);
+ },
+ // step 4
+ function(targetds, root) {
+ var newnode = RDF.GetResource(ZOO_NS + 'mammals/chimpanzee');
+ targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+ RDF.GetLiteral('Chimpanzee'), true);
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'mammals'));
+ container.InsertElementAt(newnode, '4', true);
+ },
+ // step 5
+ function(targetds, root) {
+ targetds.Assert(RDF.GetResource(ZOO_NS + 'mammals/chimpanzee'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ RDF.GetLiteral('3'), true);
+ }
+];
+]]>
+</script>
+
+<div xmlns="http://www.w3.org/1999/xhtml" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals"><template xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="template"><rule><conditions><content uri="?uri"/><member container="?uri" child="?animal"/><triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/><where subject="?name" rel="contains" value="an"/></conditions><bindings><binding subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#specimens" object="?specimens"/></bindings><action><p xmlns="http://www.w3.org/1999/xhtml" uri="?animal"><textnode xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" value="?name"/><span title="?specimens"/></p></action></rule><rule><conditions><content uri="?uri"/><member container="?uri" child="?animal"/><triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/></conditions><bindings><binding subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#specimens" object="?specimens"/></bindings><action><div xmlns="http://www.w3.org/1999/xhtml" uri="?animal" title="?name"><em><textnode xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" value="?specimens"/></em></div></action></rule></template></div>
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_htmlelementquerysyntaxrecursive.xul b/dom/xul/templates/tests/chrome/test_tmpl_htmlelementquerysyntaxrecursive.xul
new file mode 100644
index 000000000..b03f20ce2
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_htmlelementquerysyntaxrecursive.xul
@@ -0,0 +1,81 @@
+<?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"?>
+
+<!--
+ html element - query syntax recursive
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output" xmlns:html="http://www.w3.org/1999/xhtml">
+ <html:strong id="http://www.some-fictitious-zoo.com/arachnids" container="true" empty="false">Arachnids</html:strong>
+ <html:strong step="-3" id="http://www.some-fictitious-zoo.com/birds" container="true" empty="false">
+ Birds
+ <html:span id="http://www.some-fictitious-zoo.com/birds/barnowl">Barn Owl</html:span>
+ </html:strong>
+ <html:strong step="1" id="http://www.some-fictitious-zoo.com/insects">Insects</html:strong>
+ <html:strong id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false">
+ Mammals
+ <html:span id="http://www.some-fictitious-zoo.com/mammals/lion">Lion</html:span>
+ <html:span id="http://www.some-fictitious-zoo.com/mammals/hippopotamus">HIPPOPOTAMUS</html:span>
+ <html:span step="2" id="http://www.some-fictitious-zoo.com/mammals/koala">Koala</html:span>
+ <html:span id="http://www.some-fictitious-zoo.com/mammals/polarbear">Polar Bear</html:span>
+ <html:span id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo">Nine-banded Armadillo</html:span>
+ <html:span id="http://www.some-fictitious-zoo.com/mammals/gorilla">Gorilla</html:span>
+ </html:strong>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="html element - query syntax recursive";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ var newnode = RDF.GetResource(ZOO_NS + 'insects');
+ targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+ RDF.GetLiteral('Insects'), true);
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'all-animals'));
+ container.InsertElementAt(newnode, '3', true);
+ },
+ // 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);
+ },
+ // step 3
+ function(targetds, root) {
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'all-animals'));
+ var removednode = container.RemoveElementAt('2', true);
+ targetds.Unassert(removednode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+ RDF.GetLiteral('Birds'), true);
+ }
+];
+]]>
+</script>
+
+<div xmlns="http://www.w3.org/1999/xhtml" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/all-animals"><template xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="template"><query><content uri="?uri"/><member container="?uri" child="?child"/><triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/></query><rule><conditions><where subject="?name" rel="endswith" multiple="true" value="mals,ects,nids,irds"/></conditions><action><strong xmlns="http://www.w3.org/1999/xhtml" uri="?child"><textnode xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" value="?name"/></strong></action></rule><rule><conditions><where subject="?uri" rel="equals" negate="true" value="http://www.some-fictitious-zoo.com/all-animals"/><where subject="?name" rel="contains" ignorecase="true" value="o"/></conditions><action><span xmlns="http://www.w3.org/1999/xhtml" uri="?child"><textnode xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" value="?name"/></span></action></rule></template></div>
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_htmlelementquerysyntaxwithmultiplerules.xul b/dom/xul/templates/tests/chrome/test_tmpl_htmlelementquerysyntaxwithmultiplerules.xul
new file mode 100644
index 000000000..e0ef6a732
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_htmlelementquerysyntaxwithmultiplerules.xul
@@ -0,0 +1,102 @@
+<?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"?>
+
+<!--
+ html element - query syntax with multiple rules
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output" xmlns:html="http://www.w3.org/1999/xhtml">
+ <html:span step="-2" id="http://www.some-fictitious-zoo.com/mammals/lion">Lion</html:span>
+ <html:span id="http://www.some-fictitious-zoo.com/mammals/hippopotamus">HIPPOPOTAMUS</html:span>
+ <vbox>
+ <html:p step="2" id="http://www.some-fictitious-zoo.com/mammals/lion">
+ <html:span title="Lion"/>
+ </html:p>
+ <html:p id="http://www.some-fictitious-zoo.com/mammals/africanelephant">
+ <html:span title="African Elephant"/>
+ </html:p>
+ <html:p step="-1" id="http://www.some-fictitious-zoo.com/mammals/polarbear">
+ <html:span title="Polar Bear"/>
+ </html:p>
+ <html:p id="http://www.some-fictitious-zoo.com/mammals/gorilla">
+ <html:span title="Gorilla"/>
+ </html:p>
+ </vbox>
+ <html:span step="5" id="http://www.some-fictitious-zoo.com/mammals/chimpanzee">Chimpanzee</html:span>
+ <html:span id="http://www.some-fictitious-zoo.com/mammals/llama">LLAMA</html:span>
+ <html:span step="1" id="http://www.some-fictitious-zoo.com/mammals/polarbear">Polar Bear</html:span>
+ <html:span id="http://www.some-fictitious-zoo.com/mammals/aardvark">aardvark</html:span>
+ <html:span step="-3" id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo">Nine-banded Armadillo</html:span>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="html element - query syntax with multiple rules";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = true;
+var notWorkingYetDynamic = true;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'mammals/polarbear');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#specimens');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'mammals/polarbear'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ oldval, RDF.GetLiteral('5'));
+ },
+ // step 2
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'mammals/lion');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#specimens');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'mammals/lion'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ oldval, RDF.GetLiteral('9'));
+ },
+ // step 3
+ function(targetds, root) {
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'mammals'));
+ var removednode = container.RemoveElementAt('7', true);
+ targetds.Unassert(removednode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+ RDF.GetLiteral('Nine-banded Armadillo'), true);
+ },
+ // step 4
+ function(targetds, root) {
+ var newnode = RDF.GetResource(ZOO_NS + 'mammals/chimpanzee');
+ targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+ RDF.GetLiteral('Chimpanzee'), true);
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'mammals'));
+ container.InsertElementAt(newnode, '4', true);
+ },
+ // step 5
+ function(targetds, root) {
+ targetds.Assert(RDF.GetResource(ZOO_NS + 'mammals/chimpanzee'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ RDF.GetLiteral('3'), true);
+ }
+];
+]]>
+</script>
+
+<div xmlns="http://www.w3.org/1999/xhtml" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals"><template xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="template"><query><content uri="?uri"/><member container="?uri" child="?animal"/><triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/><triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#specimens" object="?specimens"/></query><rule><conditions id="conditions"><where subject="?specimens" rel="greater" value="6"/></conditions><action><vbox><p xmlns="http://www.w3.org/1999/xhtml" uri="?animal"><span title="?name"/></p></vbox></action></rule><rule><action><span xmlns="http://www.w3.org/1999/xhtml" uri="?animal"><textnode xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" value="?name"/></span></action></rule></template></div>
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_htmlelementsimplesyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_htmlelementsimplesyntax.xul
new file mode 100644
index 000000000..95c016fe8
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_htmlelementsimplesyntax.xul
@@ -0,0 +1,81 @@
+<?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"?>
+
+<!--
+ html element - simple syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output" xmlns:html="http://www.w3.org/1999/xhtml">
+ <html:p step="3" id="http://www.some-fictitious-zoo.com/birds/wren" title="Wren"/>
+ <html:p id="http://www.some-fictitious-zoo.com/birds/emu" title="Emu"/>
+ <html:p step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl" title="Barn Owl"/>
+ <html:p id="http://www.some-fictitious-zoo.com/birds/raven" title="Raven"/>
+ <html:p step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx" title="Archaeopteryx"/>
+ <html:p step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" title="Emperor Penguin"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="html element - simple syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<div xmlns="http://www.w3.org/1999/xhtml" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds"><template xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><p xmlns="http://www.w3.org/1999/xhtml" uri="rdf:*" title="rdf:http://www.some-fictitious-zoo.com/rdf#name"/></template></div>
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_htmlelementsimplesyntaxusingatextnode.xul b/dom/xul/templates/tests/chrome/test_tmpl_htmlelementsimplesyntaxusingatextnode.xul
new file mode 100644
index 000000000..14d8bab38
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_htmlelementsimplesyntaxusingatextnode.xul
@@ -0,0 +1,81 @@
+<?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"?>
+
+<!--
+ html element - simple syntax using a textnode
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output" xmlns:html="http://www.w3.org/1999/xhtml">
+ <html:p step="3" id="http://www.some-fictitious-zoo.com/birds/wren">Wren</html:p>
+ <html:p id="http://www.some-fictitious-zoo.com/birds/emu">Emu</html:p>
+ <html:p step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl">Barn Owl</html:p>
+ <html:p id="http://www.some-fictitious-zoo.com/birds/raven">Raven</html:p>
+ <html:p step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx">Archaeopteryx</html:p>
+ <html:p step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin">Emperor Penguin</html:p>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="html element - simple syntax using a textnode";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<div xmlns="http://www.w3.org/1999/xhtml" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds"><template xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><p xmlns="http://www.w3.org/1999/xhtml" uri="rdf:*"><textnode xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/></p></template></div>
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_invalidqp.xul b/dom/xul/templates/tests/chrome/test_tmpl_invalidqp.xul
new file mode 100644
index 000000000..2231f2067
--- /dev/null
+++ b/dom/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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output"/>
+
+<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 = document.getElementById("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="rdf:null"
+ 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>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_listboxelement.xul b/dom/xul/templates/tests/chrome/test_tmpl_listboxelement.xul
new file mode 100644
index 000000000..e754b1542
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_listboxelement.xul
@@ -0,0 +1,117 @@
+<?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"?>
+
+<!--
+ listbox element
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <listcols>
+ <listcol flex="1"/>
+ <listcol flex="1"/>
+ </listcols>
+ <listitem step="3" id="http://www.some-fictitious-zoo.com/birds/wren">
+ <listcell label="Wren"/>
+ <listcell label=""/>
+ </listitem>
+ <listitem id="http://www.some-fictitious-zoo.com/birds/emu">
+ <listcell label="Emu"/>
+ <listcell label="Dromaius novaehollandiae"/>
+ </listitem>
+ <listitem step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl">
+ <listcell label="Barn Owl"/>
+ <listcell label="Tyto alba"/>
+ </listitem>
+ <listitem id="http://www.some-fictitious-zoo.com/birds/raven">
+ <listcell label="Raven"/>
+ <listcell label="Corvus corax"/>
+ </listitem>
+ <listitem step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx">
+ <listcell label="Archaeopteryx"/>
+ <listcell label=""/>
+ </listitem>
+ <listitem step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin">
+ <listcell label="Emperor Penguin"/>
+ <listcell label=""/>
+ </listitem>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.expectAssertions(4);
+
+SimpleTest.waitForExplicitFinish();
+
+var testid ="listbox element";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<listbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<listcols>
+<listcol flex="1"/>
+<listcol flex="1"/>
+</listcols>
+<template>
+<listitem uri="rdf:*">
+<listcell label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+<listcell label="rdf:http://www.some-fictitious-zoo.com/rdf#species"/>
+</listitem>
+</template>
+</listbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_literalasmember.xul b/dom/xul/templates/tests/chrome/test_tmpl_literalasmember.xul
new file mode 100644
index 000000000..5e2ffe326
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_literalasmember.xul
@@ -0,0 +1,52 @@
+<?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"?>
+
+<!--
+ literal as member
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output"/>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="literal as member";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/arachnids">
+<template id="template">
+<rule id="rule">
+<conditions id="conditions">
+<content uri="?start"/>
+<member container="?start" child="?animal"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</conditions>
+<action id="action">
+<label uri="?name" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_membervariablechanged.xul b/dom/xul/templates/tests/chrome/test_tmpl_membervariablechanged.xul
new file mode 100644
index 000000000..c231b8670
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_membervariablechanged.xul
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ member variable changed
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <button id="http://www.some-fictitious-zoo.com/birds/emu" label="http://www.some-fictitious-zoo.com/birds/emu Emu"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/barnowl" label="http://www.some-fictitious-zoo.com/birds/barnowl Barn Owl"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/raven" label="http://www.some-fictitious-zoo.com/birds/raven Raven"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="member variable changed";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template member="?child">
+<rule>
+<button uri="?child" label="?child rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_membervariablesubstitution.xul b/dom/xul/templates/tests/chrome/test_tmpl_membervariablesubstitution.xul
new file mode 100644
index 000000000..512717934
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_membervariablesubstitution.xul
@@ -0,0 +1,54 @@
+<?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"?>
+
+<!--
+ member variable substitution
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="http://www.some-fictitious-zoo.com/arachnids/tarantula"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="member variable substitution";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/arachnids">
+<template id="template">
+<rule id="rule">
+<conditions id="conditions">
+<content uri="?start"/>
+<member container="?start" child="?animal"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?animal"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_menuelement.xul b/dom/xul/templates/tests/chrome/test_tmpl_menuelement.xul
new file mode 100644
index 000000000..eecb6a7ed
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_menuelement.xul
@@ -0,0 +1,90 @@
+<?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"?>
+
+<!--
+ menu element
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <menupopup>
+ <menuitem step="3" id="http://www.some-fictitious-zoo.com/birds/wren" label="Wren"/>
+ <menuitem id="http://www.some-fictitious-zoo.com/birds/emu" label="Emu"/>
+ <menuitem step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl" label="Barn Owl"/>
+ <menuitem id="http://www.some-fictitious-zoo.com/birds/raven" label="Raven"/>
+ <menuitem step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx" label="Archaeopteryx"/>
+ <menuitem step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" label="Emperor Penguin"/>
+ </menupopup>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="menu element";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = true;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<button xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" type="menu" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template>
+<menupopup>
+<menuitem uri="rdf:*" label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</menupopup>
+</template>
+</button>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_menuelementrecursive.xul b/dom/xul/templates/tests/chrome/test_tmpl_menuelementrecursive.xul
new file mode 100644
index 000000000..1597e3164
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_menuelementrecursive.xul
@@ -0,0 +1,121 @@
+<?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"?>
+
+<!--
+ menu element recursive
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <menupopup>
+ <menuitem id="http://www.some-fictitious-zoo.com/arachnids" label="Arachnids" container="true" empty="false"/>
+ <menu step="-2" id="http://www.some-fictitious-zoo.com/birds" label="Birds" container="true" empty="false"/>
+ <menu step="2" id="http://www.some-fictitious-zoo.com/birds" label="Birds" container="true" empty="false" open="true">
+ <menupopup>
+ <menuitem step="4" id="http://www.some-fictitious-zoo.com/birds/wren" label="Wren"/>
+ <menuitem id="http://www.some-fictitious-zoo.com/birds/emu" label="Emu"/>
+ <menuitem step="-5" id="http://www.some-fictitious-zoo.com/birds/barnowl" label="Barn Owl"/>
+ <menuitem id="http://www.some-fictitious-zoo.com/birds/raven" label="Raven"/>
+ <menuitem step="3" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx" label="Archaeopteryx"/>
+ <menuitem id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" label="Emperor Penguin"/>
+ </menupopup>
+ </menu>
+ <menuitem id="http://www.some-fictitious-zoo.com/crustaceans" label="Crustaceans" container="true" empty="true"/>
+ <menuitem id="http://www.some-fictitious-zoo.com/fish" label="Fish" container="true" empty="false"/>
+ <menuitem id="http://www.some-fictitious-zoo.com/mammals" label="Mammals" container="true" empty="false"/>
+ <menuitem id="http://www.some-fictitious-zoo.com/reptiles" label="Reptiles" container="true" empty="false"/>
+ </menupopup>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="menu element recursive";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = true;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ // nothing should change at this step as the submenu is not open
+ 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);
+ },
+ // step 2
+ function(targetds, root) {
+ // open the submenu
+ root.lastChild.firstChild.nextSibling.open = true;
+ },
+ // step 3
+ 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);
+ root.lastChild.firstChild.nextSibling.open = true;
+ },
+ // step 4
+ 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);
+ root.lastChild.firstChild.nextSibling.open = true;
+ },
+ // step 5
+ 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);
+ root.lastChild.firstChild.nextSibling.open = true;
+ }
+];
+]]>
+</script>
+
+<button xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" type="menu" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/all-animals">
+<template>
+<rule iscontainer="true" zoo:name="Birds" xmlns:zoo="http://www.some-fictitious-zoo.com/rdf#">
+<menupopup>
+<menu uri="rdf:*" label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</menupopup>
+</rule>
+<rule parent="button">
+<menupopup>
+<menuitem uri="rdf:*" label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</menupopup>
+</rule>
+<rule parent="menu">
+<menupopup>
+<menuitem uri="rdf:*" label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</menupopup>
+</rule>
+</template>
+</button>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_menulistelement.xul b/dom/xul/templates/tests/chrome/test_tmpl_menulistelement.xul
new file mode 100644
index 000000000..bebc7b970
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_menulistelement.xul
@@ -0,0 +1,90 @@
+<?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"?>
+
+<!--
+ menulist element
+-->
+
+<window title="XUL Template Tests" width="500" height="600"
+ onload="$('root').selectedItem = null; test_template();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <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;"/>
+
+ <data id="output">
+ <menupopup>
+ <menuitem step="3" id="http://www.some-fictitious-zoo.com/birds/wren" label="Wren"/>
+ <menuitem id="http://www.some-fictitious-zoo.com/birds/emu" label="Emu"/>
+ <menuitem step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl" label="Barn Owl"/>
+ <menuitem id="http://www.some-fictitious-zoo.com/birds/raven" label="Raven"/>
+ <menuitem step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx" label="Archaeopteryx"/>
+ <menuitem step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" label="Emperor Penguin"/>
+ </menupopup>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="menulist element";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<menulist xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template>
+<menupopup>
+<menuitem uri="rdf:*" label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</menupopup>
+</template>
+</menulist>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_mixedsyntaxiscontainer.xul b/dom/xul/templates/tests/chrome/test_tmpl_mixedsyntaxiscontainer.xul
new file mode 100644
index 000000000..446fa77ec
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_mixedsyntaxiscontainer.xul
@@ -0,0 +1,67 @@
+<?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"?>
+
+<!--
+ mixed syntax - iscontainer
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <checkbox id="http://www.some-fictitious-zoo.com/humans/sarah" label="Sarah"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false" label="Mammals"/>
+ <button id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true" label="Crustaceans"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/birds/emu" label="Emu"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="mixed syntax - iscontainer";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ targetds.Unassert(RDF.GetResource(ZOO_NS + 'humans/sarah'),
+ RDF.GetResource(ZOO_NS + 'rdf#pets'),
+ RDF.GetResource(ZOO_NS + 'sarahs-pets'), true);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/marked" flags="dont-recurse">
+<template id="template">
+<rule id="rule1" iscontainer="true">
+<button uri="rdf:*" label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</rule>
+<rule>
+<conditions>
+<content uri="?uri"/>
+<member container="?uri" child="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</conditions>
+<action>
+<checkbox uri="?child" label="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_mixedsyntaxiscontainerisempty.xul b/dom/xul/templates/tests/chrome/test_tmpl_mixedsyntaxiscontainerisempty.xul
new file mode 100644
index 000000000..2c3a3e23c
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_mixedsyntaxiscontainerisempty.xul
@@ -0,0 +1,60 @@
+<?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"?>
+
+<!--
+ mixed syntax - iscontainer isempty
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <checkbox id="http://www.some-fictitious-zoo.com/humans/sarah" label="Sarah"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false" label="Mammals"/>
+ <label id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true" value="Crustaceans"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/birds/emu" label="Emu"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="mixed syntax - iscontainer isempty";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/marked" flags="dont-recurse">
+<template id="template">
+<rule id="rule1" iscontainer="true" isempty="true">
+<label uri="rdf:*" value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</rule>
+<rule>
+<conditions>
+<content uri="?uri"/>
+<member container="?uri" child="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</conditions>
+<action>
+<checkbox uri="?child" label="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_mixedsyntaxisempty.xul b/dom/xul/templates/tests/chrome/test_tmpl_mixedsyntaxisempty.xul
new file mode 100644
index 000000000..25b5d59a0
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_mixedsyntaxisempty.xul
@@ -0,0 +1,60 @@
+<?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"?>
+
+<!--
+ mixed syntax - isempty
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/humans/sarah" value="Sarah"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false" label="Mammals"/>
+ <label id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true" value="Crustaceans"/>
+ <label id="http://www.some-fictitious-zoo.com/birds/emu" value="Emu"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="mixed syntax - isempty";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/marked" flags="dont-recurse">
+<template id="template">
+<rule id="rule1" isempty="true">
+<label uri="rdf:*" value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</rule>
+<rule>
+<conditions>
+<content uri="?uri"/>
+<member container="?uri" child="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</conditions>
+<action>
+<checkbox uri="?child" label="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_noaction.xul b/dom/xul/templates/tests/chrome/test_tmpl_noaction.xul
new file mode 100644
index 000000000..f03623e09
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_noaction.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"?>
+
+<!--
+ no action
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output"/>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="no action";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template>
+<query>
+<content uri="?uri"/>
+<member container="?uri" child="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<button uri="?child" label="?name"/>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_noactionuriattribute.xul b/dom/xul/templates/tests/chrome/test_tmpl_noactionuriattribute.xul
new file mode 100644
index 000000000..bd14bee1f
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_noactionuriattribute.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ no action uri attribute
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label value="Tarantula"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="no action uri attribute";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'arachnids/tarantula');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#name');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'arachnids/tarantula'),
+ RDF.GetResource(ZOO_NS + 'rdf#name'),
+ oldval, RDF.GetLiteral('Hairy Spider'));
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/arachnids">
+<template id="template">
+<label value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_parentconditions.xul b/dom/xul/templates/tests/chrome/test_tmpl_parentconditions.xul
new file mode 100644
index 000000000..dae20a830
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_parentconditions.xul
@@ -0,0 +1,113 @@
+<?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"?>
+
+<!--
+ parent - conditions
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <box step="-3" id="http://www.some-fictitious-zoo.com/birds" container="true" empty="false">
+ <label value="Birds"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/barnowl" label="Barn Owl"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/raven" label="Raven"/>
+ </box>
+ <box step="1" id="http://www.some-fictitious-zoo.com/insects">
+ <label value="Insects"/>
+ </box>
+ <box id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true">
+ <label value="Crustaceans"/>
+ </box>
+ <box id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false">
+ <label value="Mammals"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/africanelephant" label="African Elephant"/>
+ <button step="2" id="http://www.some-fictitious-zoo.com/mammals/koala" label="Koala"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/polarbear" label="Polar Bear"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/aardvark" label="aardvark"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" label="Nine-banded Armadillo"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/gorilla" label="Gorilla"/>
+ </box>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="parent - conditions";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ var newnode = RDF.GetResource(ZOO_NS + 'insects');
+ targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+ RDF.GetLiteral('Insects'), true);
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'all-animals'));
+ container.InsertElementAt(newnode, '3', true);
+ },
+ // 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);
+ },
+ // step 3
+ function(targetds, root) {
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'all-animals'));
+ var removednode = container.RemoveElementAt('2', true);
+ targetds.Unassert(removednode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+ RDF.GetLiteral('Birds'), true);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/all-animals">
+<template>
+<query>
+<content uri="?uri"/>
+<member container="?uri" child="?parent"/>
+<triple subject="?parent" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<rule>
+<conditions parent="box">
+<where subject="?name" rel="contains" value="a"/>
+</conditions>
+<action>
+<button uri="?parent" label="?name"/>
+</action>
+</rule>
+<rule>
+<conditions>
+<where subject="?name" rel="equals" multiple="true" value="Mammals,Crustaceans,Birds,Insects"/>
+</conditions>
+<action>
+<box uri="?parent">
+<label value="?name"/>
+</box>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_parentcontenttag.xul b/dom/xul/templates/tests/chrome/test_tmpl_parentcontenttag.xul
new file mode 100644
index 000000000..afcb1d34a
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_parentcontenttag.xul
@@ -0,0 +1,114 @@
+<?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"?>
+
+<!--
+ parent - content tag
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <groupbox step="-3" id="http://www.some-fictitious-zoo.com/birds" container="true" empty="false">
+ <caption value="Birds"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/birds/barnowl" label="Barn Owl"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/birds/raven" label="Raven"/>
+ </groupbox>
+ <groupbox step="1" id="http://www.some-fictitious-zoo.com/insects">
+ <caption value="Insects"/>
+ </groupbox>
+ <groupbox id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true">
+ <caption value="Crustaceans"/>
+ </groupbox>
+ <groupbox id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false">
+ <caption value="Mammals"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals/africanelephant" label="African Elephant"/>
+ <checkbox step="2" id="http://www.some-fictitious-zoo.com/mammals/koala" label="Koala"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals/polarbear" label="Polar Bear"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals/aardvark" label="aardvark"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" label="Nine-banded Armadillo"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals/gorilla" label="Gorilla"/>
+ </groupbox>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="parent - content tag";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ var newnode = RDF.GetResource(ZOO_NS + 'insects');
+ targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+ RDF.GetLiteral('Insects'), true);
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'all-animals'));
+ container.InsertElementAt(newnode, '3', true);
+ },
+ // 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);
+ },
+ // step 3
+ function(targetds, root) {
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'all-animals'));
+ var removednode = container.RemoveElementAt('2', true);
+ targetds.Unassert(removednode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+ RDF.GetLiteral('Birds'), true);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/all-animals">
+<template>
+<rule>
+<conditions>
+<content uri="?uri" tag="groupbox"/>
+<member container="?uri" child="?parent"/>
+<triple subject="?parent" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+<where subject="?name" rel="contains" value="a"/>
+</conditions>
+<action>
+<checkbox uri="?parent" label="?name"/>
+</action>
+</rule>
+<rule>
+<conditions>
+<content uri="?uri"/>
+<member container="?uri" child="?parent"/>
+<triple subject="?parent" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+<where subject="?name" rel="equals" multiple="true" value="Mammals,Crustaceans,Birds,Insects"/>
+</conditions>
+<action>
+<groupbox uri="?parent">
+<caption value="?name"/>
+</groupbox>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_parentsimplesyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_parentsimplesyntax.xul
new file mode 100644
index 000000000..c5d94e0e2
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_parentsimplesyntax.xul
@@ -0,0 +1,110 @@
+<?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"?>
+
+<!--
+ parent - simple syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <hbox step="-3" id="http://www.some-fictitious-zoo.com/birds" container="true" empty="false">
+ <label value="Birds"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/emu" label="Emu"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/barnowl" label="Barn Owl"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/raven" label="Raven"/>
+ </hbox>
+ <hbox step="1" id="http://www.some-fictitious-zoo.com/insects">
+ <label value="Insects"/>
+ </hbox>
+ <hbox id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true">
+ <label value="Crustaceans"/>
+ </hbox>
+ <hbox id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false">
+ <label value="Mammals"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/lion" label="Lion"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" label="HIPPOPOTAMUS"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/africanelephant" label="African Elephant"/>
+ <button step="2" id="http://www.some-fictitious-zoo.com/mammals/koala" label="Koala"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/llama" label="LLAMA"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/polarbear" label="Polar Bear"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/aardvark" label="aardvark"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" label="Nine-banded Armadillo"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/gorilla" label="Gorilla"/>
+ </hbox>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="parent - simple syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ var newnode = RDF.GetResource(ZOO_NS + 'insects');
+ targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+ RDF.GetLiteral('Insects'), true);
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'all-animals'));
+ container.InsertElementAt(newnode, '3', true);
+ },
+ // 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);
+ },
+ // step 3
+ function(targetds, root) {
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'all-animals'));
+ var removednode = container.RemoveElementAt('2', true);
+ targetds.Unassert(removednode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+ RDF.GetLiteral('Birds'), true);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/all-animals">
+<template member="?parent">
+<rule parent="hbox">
+<button uri="?parent" label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</rule>
+<rule>
+<conditions>
+<content uri="?uri"/>
+<member container="?uri" child="?parent"/>
+<triple subject="?parent" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+<where subject="?name" rel="equals" multiple="true" value="Mammals,Crustaceans,Birds,Insects"/>
+</conditions>
+<action>
+<hbox uri="?parent">
+<label value="?name"/>
+</hbox>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_query3triples.xul b/dom/xul/templates/tests/chrome/test_tmpl_query3triples.xul
new file mode 100644
index 000000000..c0114effe
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_query3triples.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ query - 3 triples
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output" unordered="true">
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Robert likes Tarantulas"/>
+ <label id="http://www.some-fictitious-zoo.com/reptiles/anaconda" value="Robert likes Anacondas"/>
+ <label id="http://www.some-fictitious-zoo.com/reptiles/chameleon" value="Robert likes Chameleons"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="Robert likes African Elephants"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query - 3 triples";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/humans/robert">
+<template id="template">
+<query id="query">
+<content uri="?uri"/>
+<triple subject="?uri" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?humanname"/>
+<triple subject="?uri" predicate="http://www.some-fictitious-zoo.com/rdf#favoriteAnimal" object="?favoriteAnimal"/>
+<triple subject="?favoriteAnimal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?animalname"/>
+</query>
+<rule>
+<conditions id="conditions"/>
+<action>
+<label uri="?favoriteAnimal" value="?humanname likes ?animalname^s"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_query3tripleswherecontains.xul b/dom/xul/templates/tests/chrome/test_tmpl_query3tripleswherecontains.xul
new file mode 100644
index 000000000..98006fc0d
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_query3tripleswherecontains.xul
@@ -0,0 +1,111 @@
+<?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"?>
+
+<!--
+ query - 3 triples - where contains
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Robert likes Tarantulas"/>
+ <label step="-4" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="Robert likes African Elephants"/>
+ <label step="5" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="Robert likes African Elephants"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query - 3 triples - where contains";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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="rdf:null" ref="http://www.some-fictitious-zoo.com/humans/robert">
+<template id="template">
+<query id="query">
+<content uri="?uri"/>
+<triple subject="?uri" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?humanname"/>
+<triple subject="?uri" predicate="http://www.some-fictitious-zoo.com/rdf#favoriteAnimal" object="?favoriteAnimal"/>
+<triple subject="?favoriteAnimal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?animalname"/>
+</query>
+<rule>
+<conditions id="conditions">
+<where subject="?favoriteAnimal" rel="contains" value="ant"/>
+</conditions>
+<action>
+<label uri="?favoriteAnimal" value="?humanname likes ?animalname^s"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_querymember3tripleswhereequals.xul b/dom/xul/templates/tests/chrome/test_tmpl_querymember3tripleswhereequals.xul
new file mode 100644
index 000000000..8604a1321
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_querymember3tripleswhereequals.xul
@@ -0,0 +1,60 @@
+<?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"?>
+
+<!--
+ query - member, 3 triples - where equals
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output" unordered="true">
+ <label id="http://www.some-fictitious-zoo.com/birds/emu" value="Sarah likes Emus"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Sarah likes Polar Bears"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query - member, 3 triples - where equals";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/humans">
+<template id="template">
+<query id="query">
+<content uri="?uri"/>
+<member container="?uri" child="?human"/>
+<triple subject="?human" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?humanname"/>
+<triple subject="?human" predicate="http://www.some-fictitious-zoo.com/rdf#favoriteAnimal" object="?favoriteAnimal"/>
+<triple subject="?favoriteAnimal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?animalname"/>
+</query>
+<rule>
+<conditions id="conditions">
+<where subject="?humanname" rel="equals" value="Sarah"/>
+</conditions>
+<action>
+<label uri="?favoriteAnimal" value="?humanname likes ?animalname^s"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_querymemberandtwotriples.xul b/dom/xul/templates/tests/chrome/test_tmpl_querymemberandtwotriples.xul
new file mode 100644
index 000000000..9f3f36288
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_querymemberandtwotriples.xul
@@ -0,0 +1,58 @@
+<?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"?>
+
+<!--
+ query - member and two triples
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output" unordered="true">
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Tarantula"/>
+ <label id="http://www.some-fictitious-zoo.com/reptiles/anaconda" value="Anaconda"/>
+ <label id="http://www.some-fictitious-zoo.com/reptiles/chameleon" value="Chameleon"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/birds/emu" value="Emu"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query - member and two triples";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/humans">
+<template id="template">
+<query id="query">
+<content uri="?uri"/>
+<member container="?uri" child="?human"/>
+<triple subject="?human" predicate="http://www.some-fictitious-zoo.com/rdf#favoriteAnimal" object="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+,<action>
+<label uri="?child" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_querymembertriplemembertriple.xul b/dom/xul/templates/tests/chrome/test_tmpl_querymembertriplemembertriple.xul
new file mode 100644
index 000000000..b5b3acf79
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_querymembertriplemembertriple.xul
@@ -0,0 +1,64 @@
+<?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"?>
+
+<!--
+ query - member, triple, member, triple
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output" unordered="true">
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Tarantula"/>
+ <label id="http://www.some-fictitious-zoo.com/reptiles/chameleon" value="Chameleon"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label step="-1" id="http://www.some-fictitious-zoo.com/birds/emu" value="Emu"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query - member, triple, member, triple";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ targetds.Unassert(RDF.GetResource(ZOO_NS + 'humans/sarah'),
+ RDF.GetResource(ZOO_NS + 'rdf#pets'),
+ RDF.GetResource(ZOO_NS + 'sarahs-pets'), true);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/humans">
+<template id="template">
+<query id="query">
+<content uri="?uri"/>
+<member container="?uri" child="?human"/>
+<triple subject="?human" predicate="http://www.some-fictitious-zoo.com/rdf#pets" object="?pets"/>
+<member container="?pets" child="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+,<action>
+<label uri="?child" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_queryresourcematch.xul b/dom/xul/templates/tests/chrome/test_tmpl_queryresourcematch.xul
new file mode 100644
index 000000000..b13ccdb7f
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_queryresourcematch.xul
@@ -0,0 +1,58 @@
+<?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"?>
+
+<!--
+ query - resource match
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/arachnids" container="true" empty="false" value="Arachnids"/>
+ <label id="http://www.some-fictitious-zoo.com/birds" container="true" empty="false" value="Birds"/>
+ <label id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true" value="Crustaceans"/>
+ <label id="http://www.some-fictitious-zoo.com/fish" container="true" empty="false" value="Fish"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false" value="Mammals"/>
+ <label id="http://www.some-fictitious-zoo.com/reptiles" container="true" empty="false" value="Reptiles"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query - resource match";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/all-animals">
+<template id="template">
+<query id="query">
+<content uri="?uri"/>
+<member container="?uri" child="?child"/>
+<triple subject="?child" predicate="http://www.w3.org/1999/02/22-rdf-syntax-ns#type" object="http://www.some-fictitious-zoo.com/rdf#Class"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+,<action>
+<label uri="?child" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_queryreversetriple.xul b/dom/xul/templates/tests/chrome/test_tmpl_queryreversetriple.xul
new file mode 100644
index 000000000..633df8834
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_queryreversetriple.xul
@@ -0,0 +1,53 @@
+<?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"?>
+
+<!--
+ query - reverse triple
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/reptiles" container="true" empty="false" value="Reptiles"/>
+ <label id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true" value="Crustaceans"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query - reverse triple";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/humans/robert">
+<template id="template">
+<query id="query">
+<content uri="?uri"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#keeper" object="?uri"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+,<action>
+<label uri="?child" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_queryselfwithtriple.xul b/dom/xul/templates/tests/chrome/test_tmpl_queryselfwithtriple.xul
new file mode 100644
index 000000000..bd10d0d1f
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_queryselfwithtriple.xul
@@ -0,0 +1,51 @@
+<?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"?>
+
+<!--
+ query - self with triple
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false" value="Mammals"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query - self with triple";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals">
+<template>
+<query id="query">
+<content uri="?uri"/>
+<triple subject="?uri" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<action>
+<label uri="?uri" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_querysetone.xul b/dom/xul/templates/tests/chrome/test_tmpl_querysetone.xul
new file mode 100644
index 000000000..ed9c93843
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_querysetone.xul
@@ -0,0 +1,95 @@
+<?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"?>
+
+<!--
+ queryset - one
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <button step="3" id="http://www.some-fictitious-zoo.com/birds/wren" label="Wren"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/emu" label="Emu"/>
+ <button step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl" label="Barn Owl"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/raven" label="Raven"/>
+ <button step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx" label="Archaeopteryx"/>
+ <button step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" label="Emperor Penguin"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="queryset - one";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template id="template">
+<queryset>
+<query>
+<content uri="?uri"/>
+<member container="?uri" child="?animal"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<action>
+<button uri="?animal" label="?name"/>
+</action>
+</queryset>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_querysettwo.xul b/dom/xul/templates/tests/chrome/test_tmpl_querysettwo.xul
new file mode 100644
index 000000000..1f6627931
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_querysettwo.xul
@@ -0,0 +1,117 @@
+<?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"?>
+
+<!--
+ queryset - two
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <button step="3" id="http://www.some-fictitious-zoo.com/birds/wren" label="Wren"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/emu" label="Emu"/>
+ <checkbox step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl" label="Barn Owl"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/raven" label="Raven"/>
+ <button step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx" label="Archaeopteryx"/>
+ <button step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" label="Emperor Penguin"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="queryset - two";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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" flags="logging"
+ datasources="rdf:null" 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>
+<action>
+<checkbox uri="?animal" label="?name"/>
+</action>
+</queryset>
+<queryset>
+<query>
+<content uri="?uri"/>
+<member container="?uri" child="?animal"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<action>
+<button uri="?animal" label="?name"/>
+</action>
+</queryset>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_querysettwowithcondition.xul b/dom/xul/templates/tests/chrome/test_tmpl_querysettwowithcondition.xul
new file mode 100644
index 000000000..29b19f984
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_querysettwowithcondition.xul
@@ -0,0 +1,149 @@
+<?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"?>
+
+<!--
+ queryset - two with condition
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <hbox step="-6" id="http://www.some-fictitious-zoo.com/birds/emu">
+ <label value="Emu"/>
+ <label value="Dromaius novaehollandiae"/>
+ </hbox>
+ <button step="6" id="http://www.some-fictitious-zoo.com/birds/emu" label="No Emus Currently"/>
+ <hbox step="3" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx">
+ <label value="Archaeopteryx"/>
+ <label value="Archaeopteryx lithographica"/>
+ </hbox>
+ <hbox step="-2" id="http://www.some-fictitious-zoo.com/birds/barnowl">
+ <label value="Barn Owl"/>
+ <label value="Tyto alba"/>
+ </hbox>
+ <button step="-5" id="http://www.some-fictitious-zoo.com/birds/raven" label="No Ravens Currently"/>
+ <hbox step="5" id="http://www.some-fictitious-zoo.com/birds/raven">
+ <label step="-7" value="Raven"/>
+ <label step="7" value="Crow"/>
+ <label value="Corvus corax"/>
+ </hbox>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="queryset - two with condition";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ 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, '2', true);
+ },
+ // step 2
+ 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);
+ },
+ // step 3
+ function(targetds, root) {
+ targetds.Assert(RDF.GetResource(ZOO_NS + 'birds/archaeopteryx'),
+ RDF.GetResource(ZOO_NS + 'rdf#species'),
+ RDF.GetLiteral('Archaeopteryx lithographica'), true);
+ },
+ // step 4
+ function(targetds, root) {
+ targetds.Assert(RDF.GetResource(ZOO_NS + 'birds/archaeopteryx'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ RDF.GetLiteral('5'), true);
+ },
+ // step 5
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'birds/raven');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#specimens');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'birds/raven'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ oldval, RDF.GetLiteral('2'));
+ },
+ // step 6
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'birds/emu');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#specimens');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'birds/emu'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ oldval, RDF.GetLiteral('0'));
+ },
+ // step 7
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'birds/raven');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#name');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'birds/raven'),
+ RDF.GetResource(ZOO_NS + 'rdf#name'),
+ oldval, RDF.GetLiteral('Crow'));
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template>
+<queryset>
+<query>
+<content uri="?uri"/>
+<member container="?uri" child="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#specimens" object="?specimens"/>
+</query>
+<rule>
+<conditions>
+<where subject="?specimens" rel="equals" value="0"/>
+</conditions>
+<action>
+<button uri="?child" label="No ?name^s Currently"/>
+</action>
+</rule>
+</queryset>
+<queryset>
+<query>
+<content uri="?uri"/>
+<member container="?uri" child="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#species" object="?species"/>
+</query>
+<action>
+<hbox uri="?child">
+<label value="?name^?unknown"/>
+<label value="?species^?specimens"/>
+</hbox>
+</action>
+</queryset>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_querysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_querysyntax.xul
new file mode 100644
index 000000000..713cb371c
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_querysyntax.xul
@@ -0,0 +1,63 @@
+<?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"?>
+
+<!--
+ query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="-1" id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Tarantula"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Hairy Spider"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'arachnids/tarantula');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#name');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'arachnids/tarantula'),
+ RDF.GetResource(ZOO_NS + 'rdf#name'),
+ oldval, RDF.GetLiteral('Hairy Spider'));
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/arachnids">
+<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>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_querysyntaxmultiplerules.xul b/dom/xul/templates/tests/chrome/test_tmpl_querysyntaxmultiplerules.xul
new file mode 100644
index 000000000..e1ac87c72
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_querysyntaxmultiplerules.xul
@@ -0,0 +1,115 @@
+<?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"?>
+
+<!--
+ query syntax - multiple rules
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="-2" id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <checkbox step="2" id="http://www.some-fictitious-zoo.com/mammals/lion" label="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals/africanelephant" label="African Elephant"/>
+ <label step="5" id="http://www.some-fictitious-zoo.com/mammals/chimpanzee" value="Chimpanzee"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <checkbox step="-1" id="http://www.some-fictitious-zoo.com/mammals/polarbear" label="Polar Bear"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label step="-3" id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals/gorilla" label="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query syntax - multiple rules";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'mammals/polarbear');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#specimens');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'mammals/polarbear'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ oldval, RDF.GetLiteral('5'));
+ },
+ // step 2
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'mammals/lion');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#specimens');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'mammals/lion'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ oldval, RDF.GetLiteral('9'));
+ },
+ // step 3
+ function(targetds, root) {
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'mammals'));
+ var removednode = container.RemoveElementAt('7', true);
+ targetds.Unassert(removednode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+ RDF.GetLiteral('Nine-banded Armadillo'), true);
+ },
+ // step 4
+ function(targetds, root) {
+ var newnode = RDF.GetResource(ZOO_NS + 'mammals/chimpanzee');
+ targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+ RDF.GetLiteral('Chimpanzee'), true);
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'mammals'));
+ container.InsertElementAt(newnode, '4', true);
+ },
+ // step 5
+ function(targetds, root) {
+ targetds.Assert(RDF.GetResource(ZOO_NS + 'mammals/chimpanzee'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ RDF.GetLiteral('3'), true);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals">
+<template id="template">
+<query>
+<content uri="?uri"/>
+<member container="?uri" child="?animal"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#specimens" object="?specimens"/>
+</query>
+<rule>
+<conditions id="conditions">
+<where subject="?specimens" rel="greater" value="6"/>
+</conditions>
+<action>
+<checkbox uri="?animal" label="?name"/>
+</action>
+</rule>
+<rule id="rule2">
+<action>
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_querysyntaxmultiplerulesfirstconditionall.xul b/dom/xul/templates/tests/chrome/test_tmpl_querysyntaxmultiplerulesfirstconditionall.xul
new file mode 100644
index 000000000..4509c1018
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_querysyntaxmultiplerulesfirstconditionall.xul
@@ -0,0 +1,71 @@
+<?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"?>
+
+<!--
+ query syntax - multiple rules first condition all
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals/lion" label="Lion"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" label="HIPPOPOTAMUS"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals/africanelephant" label="African Elephant"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals/llama" label="LLAMA"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals/polarbear" label="Polar Bear"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals/aardvark" label="aardvark"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" label="Nine-banded Armadillo"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals/gorilla" label="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query syntax - multiple rules first condition all";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals">
+<template id="template">
+<query>
+<content uri="?uri"/>
+<member container="?uri" child="?animal"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#specimens" object="?specimens"/>
+</query>
+<rule>
+<where subject="?animal" rel="contains" value="zoo"/>
+<action>
+<checkbox uri="?animal" label="?name"/>
+</action>
+</rule>
+<rule id="rule2">
+<conditions>
+<where subject="?name" rel="contains" value="an"/>
+</conditions>
+<action>
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_querysyntaxmultiplerulestwoconditions.xul b/dom/xul/templates/tests/chrome/test_tmpl_querysyntaxmultiplerulestwoconditions.xul
new file mode 100644
index 000000000..68060c6b4
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_querysyntaxmultiplerulestwoconditions.xul
@@ -0,0 +1,113 @@
+<?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"?>
+
+<!--
+ query syntax - multiple rules two conditions
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <checkbox step="2" id="http://www.some-fictitious-zoo.com/mammals/lion" label="Lion"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals/africanelephant" label="African Elephant"/>
+ <label step="5" id="http://www.some-fictitious-zoo.com/mammals/chimpanzee" value="Chimpanzee"/>
+ <checkbox step="-1" id="http://www.some-fictitious-zoo.com/mammals/polarbear" label="Polar Bear"/>
+ <label step="-3" id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <checkbox id="http://www.some-fictitious-zoo.com/mammals/gorilla" label="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query syntax - multiple rules two conditions";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'mammals/polarbear');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#specimens');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'mammals/polarbear'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ oldval, RDF.GetLiteral('5'));
+ },
+ // step 2
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'mammals/lion');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#specimens');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'mammals/lion'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ oldval, RDF.GetLiteral('9'));
+ },
+ // step 3
+ function(targetds, root) {
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'mammals'));
+ var removednode = container.RemoveElementAt('7', true);
+ targetds.Unassert(removednode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+ RDF.GetLiteral('Nine-banded Armadillo'), true);
+ },
+ // step 4
+ function(targetds, root) {
+ var newnode = RDF.GetResource(ZOO_NS + 'mammals/chimpanzee');
+ targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+ RDF.GetLiteral('Chimpanzee'), true);
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'mammals'));
+ container.InsertElementAt(newnode, '4', true);
+ },
+ // step 5
+ function(targetds, root) {
+ targetds.Assert(RDF.GetResource(ZOO_NS + 'mammals/chimpanzee'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ RDF.GetLiteral('3'), true);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals">
+<template id="template">
+<query>
+<content uri="?uri"/>
+<member container="?uri" child="?animal"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#specimens" object="?specimens"/>
+</query>
+<rule>
+<conditions id="conditions">
+<where subject="?specimens" rel="greater" value="6"/>
+</conditions>
+<action>
+<checkbox uri="?animal" label="?name"/>
+</action>
+</rule>
+<rule id="rule2">
+<conditions>
+<where subject="?name" rel="contains" value="an"/>
+</conditions>
+<action>
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_querytripleandmembermerge.xul b/dom/xul/templates/tests/chrome/test_tmpl_querytripleandmembermerge.xul
new file mode 100644
index 000000000..7abb67e1c
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_querytripleandmembermerge.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ query - triple and member merge
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output" unordered="true">
+ <label id="http://www.some-fictitious-zoo.com/reptiles/chameleon" value="Chameleon"/>
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Tarantula"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query - triple and member merge";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/humans/robert">
+<template id="template">
+<query id="query">
+<content uri="?uri"/>
+<triple subject="?uri" predicate="http://www.some-fictitious-zoo.com/rdf#favoriteAnimal" object="?child"/>
+<triple subject="?uri" predicate="http://www.some-fictitious-zoo.com/rdf#pets" object="?pets"/>
+<member container="?pets" child="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+,<action>
+<label uri="?child" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_querytripleobjecttosubject.xul b/dom/xul/templates/tests/chrome/test_tmpl_querytripleobjecttosubject.xul
new file mode 100644
index 000000000..3998f1796
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_querytripleobjecttosubject.xul
@@ -0,0 +1,67 @@
+<?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"?>
+
+<!--
+ query - triple object to subject
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="-1" id="http://www.some-fictitious-zoo.com/humans/robert" value="Robert"/>
+ <label step="2" id="http://www.some-fictitious-zoo.com/humans/robert" value="Robert"/>
+ <label id="http://www.some-fictitious-zoo.com/humans/sarah" value="Sarah"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query - triple object to subject";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = true;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ targetds.Unassert(RDF.GetResource(ZOO_NS + 'humans/robert'),
+ RDF.GetResource(ZOO_NS + 'rdf#favoriteAnimal'),
+ RDF.GetResource(ZOO_NS + 'arachnids/tarantula'), true);
+ },
+ // step 2
+ function(targetds, root) {
+ targetds.Assert(RDF.GetResource(ZOO_NS + 'humans/robert'),
+ RDF.GetResource(ZOO_NS + 'rdf#favoriteAnimal'),
+ RDF.GetResource(ZOO_NS + 'mammals/lion'), true);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/arachnids/tarantula">
+<template id="template">
+<query>
+<content uri="?uri"/>
+<triple subject="?human" predicate="http://www.some-fictitious-zoo.com/rdf#favoriteAnimal" object="?uri"/>
+<triple subject="?human" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?humanname"/>
+</query>
+<action>
+<label uri="?human" value="?humanname"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_querytwomembers.xul b/dom/xul/templates/tests/chrome/test_tmpl_querytwomembers.xul
new file mode 100644
index 000000000..df487a26f
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_querytwomembers.xul
@@ -0,0 +1,107 @@
+<?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"?>
+
+<!--
+ query - two members
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output" unordered="true">
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Tarantula"/>
+ <label step="3" id="http://www.some-fictitious-zoo.com/birds/wren" value="Wren"/>
+ <label id="http://www.some-fictitious-zoo.com/birds/emu" value="Emu"/>
+ <label step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl" value="Barn Owl"/>
+ <label id="http://www.some-fictitious-zoo.com/birds/raven" value="Raven"/>
+ <label step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx" value="Archaeopteryx"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" value="Emperor Penguin"/>
+ <label id="http://www.some-fictitious-zoo.com/fish/cod" value="Cod"/>
+ <label id="http://www.some-fictitious-zoo.com/fish/swordfish" value="Swordfish"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <label id="http://www.some-fictitious-zoo.com/reptiles/anaconda" value="Anaconda"/>
+ <label id="http://www.some-fictitious-zoo.com/reptiles/chameleon" value="Chameleon"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query - two members";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/all-animals">
+<template id="template">
+<query id="query">
+<content uri="?uri"/>
+<member container="?uri" child="?childone"/>
+<member container="?childone" child="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+,<action>
+<label uri="?child" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_querytwomembersfiltered.xul b/dom/xul/templates/tests/chrome/test_tmpl_querytwomembersfiltered.xul
new file mode 100644
index 000000000..fdd1efb20
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_querytwomembersfiltered.xul
@@ -0,0 +1,54 @@
+<?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"?>
+
+<!--
+ query - two members filtered
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="Mammals"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query - two members filtered";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/all-animals">
+<template id="template">
+<query id="query">
+<content uri="?uri"/>
+<member container="?uri" child="?childone"/>
+<triple subject="?childone" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+<member container="?childone" child="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="LLAMA"/>
+</query>
+,<action>
+<label uri="?child" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_querytwotriples.xul b/dom/xul/templates/tests/chrome/test_tmpl_querytwotriples.xul
new file mode 100644
index 000000000..83887358f
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_querytwotriples.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ query - two triples
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output" unordered="true">
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Tarantula"/>
+ <label id="http://www.some-fictitious-zoo.com/reptiles/anaconda" value="Anaconda"/>
+ <label id="http://www.some-fictitious-zoo.com/reptiles/chameleon" value="Chameleon"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query - two triples";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/humans/robert">
+<template id="template">
+<query id="query">
+<content uri="?uri"/>
+<triple subject="?uri" predicate="http://www.some-fictitious-zoo.com/rdf#favoriteAnimal" object="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+,<action>
+<label uri="?child" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_queryupwardsmember.xul b/dom/xul/templates/tests/chrome/test_tmpl_queryupwardsmember.xul
new file mode 100644
index 000000000..5d3658535
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_queryupwardsmember.xul
@@ -0,0 +1,54 @@
+<?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"?>
+
+<!--
+ query - upwards member
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output" unordered="true">
+ <label id="http://www.some-fictitious-zoo.com/marked" container="true" empty="false" value="Marked"/>
+ <label id="http://www.some-fictitious-zoo.com/birds" container="true" empty="false" value="Birds"/>
+ <label id="http://www.some-fictitious-zoo.com/sarahs-pets" container="true" empty="false" value="Sarah's Pets"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query - upwards member";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds/emu" flags="dont-recurse">
+<template id="template">
+<query id="query">
+<content uri="?uri"/>
+<member container="?child" child="?uri"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+,<action>
+<label uri="?child" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_queryupwardsmembertripleandfilteringtriple.xul b/dom/xul/templates/tests/chrome/test_tmpl_queryupwardsmembertripleandfilteringtriple.xul
new file mode 100644
index 000000000..1cfb689b0
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_queryupwardsmembertripleandfilteringtriple.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ query - upwards member, triple and filtering triple
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output" unordered="true">
+ <label id="http://www.some-fictitious-zoo.com/reptiles/anaconda" value="Anaconda"/>
+ <label id="http://www.some-fictitious-zoo.com/reptiles/chameleon" value="Chameleon"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query - upwards member, triple and filtering triple";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/humans/robert">
+<template id="template">
+<query id="query">
+<content uri="?uri"/>
+<triple subject="?uri" predicate="http://www.some-fictitious-zoo.com/rdf#favoriteAnimal" object="?child"/>
+<member container="?reptiles" child="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+<triple subject="?reptiles" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="Reptiles"/>
+</query>
+,<action>
+<label uri="?child" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_querywithemptyconditions.xul b/dom/xul/templates/tests/chrome/test_tmpl_querywithemptyconditions.xul
new file mode 100644
index 000000000..265a5bb14
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_querywithemptyconditions.xul
@@ -0,0 +1,117 @@
+<?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"?>
+
+<!--
+ query with empty conditions
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="3" id="http://www.some-fictitious-zoo.com/mammals/zebra" value="Zebra"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label step="-4" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label step="2" id="http://www.some-fictitious-zoo.com/mammals/koala" value="Koala"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/mammals/arctichare" value="Arctic Hare"/>
+ <label step="5" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="query with empty conditions";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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="rdf:null" 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"/>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_referenceasmember.xul b/dom/xul/templates/tests/chrome/test_tmpl_referenceasmember.xul
new file mode 100644
index 000000000..bf67ebf9d
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_referenceasmember.xul
@@ -0,0 +1,65 @@
+<?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"?>
+
+<!--
+ reference as member
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="-1" id="http://www.some-fictitious-zoo.com/arachnids" container="true" empty="false" value="Tarantula"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/arachnids" container="true" empty="false" value="Hairy Spider"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="reference as member";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'arachnids/tarantula');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#name');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'arachnids/tarantula'),
+ RDF.GetResource(ZOO_NS + 'rdf#name'),
+ oldval, RDF.GetLiteral('Hairy Spider'));
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/arachnids">
+<template id="template">
+<rule id="rule">
+<conditions id="conditions">
+<content uri="?start"/>
+<member container="?start" child="?animal"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</conditions>
+<action id="action">
+<label uri="?start" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_regenerate.xul b/dom/xul/templates/tests/chrome/test_tmpl_regenerate.xul
new file mode 100644
index 000000000..2c9aedc7b
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_regenerate.xul
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ Regenerate template by removing and appending elements
+-->
+
+<window title="XUL Template Tests" width="500" height="600"
+ onload="test_regenerate()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <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();
+
+function test_regenerate()
+{
+ var container = document.getElementById("container");
+ var node = container.firstChild;
+
+ if (node.childNodes.length != 2) {
+ setTimeout(test_regenerate, 50);
+ return;
+ }
+
+ container.removeChild(node);
+ is(node.childNodes.length, 1, "childNodes after removeChild");
+ container.appendChild(node);
+ is(node.childNodes.length, 2, "childNodes after appendChild");
+ SimpleTest.finish();
+}
+
+]]>
+</script>
+
+<vbox id="container">
+<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">
+<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>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_selfgenerationextendedsyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_selfgenerationextendedsyntax.xul
new file mode 100644
index 000000000..712cd4a44
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_selfgenerationextendedsyntax.xul
@@ -0,0 +1,66 @@
+<?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"?>
+
+<!--
+ self generation - extended syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="-1" id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Tarantula"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Hairy Spider"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="self generation - extended syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'arachnids/tarantula');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#name');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'arachnids/tarantula'),
+ RDF.GetResource(ZOO_NS + 'rdf#name'),
+ oldval, RDF.GetLiteral('Hairy Spider'));
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/arachnids/tarantula">
+<template id="template">
+<query>
+<content uri="?uri"/>
+</query>
+<rule>
+<bindings>
+<binding subject="?uri" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</bindings>
+<action>
+<label uri="?uri" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_selfgenerationsimplesyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_selfgenerationsimplesyntax.xul
new file mode 100644
index 000000000..e644d16ac
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_selfgenerationsimplesyntax.xul
@@ -0,0 +1,53 @@
+<?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"?>
+
+<!--
+ self generation - simple syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output"/>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="self generation - simple syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'arachnids/tarantula');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#name');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'arachnids/tarantula'),
+ RDF.GetResource(ZOO_NS + 'rdf#name'),
+ oldval, RDF.GetLiteral('Hairy Spider'));
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/arachnids/tarantula">
+<template id="template">
+<button uri="?" label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxenclosedinacontainer.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxenclosedinacontainer.xul
new file mode 100644
index 000000000..18e8b72a9
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxenclosedinacontainer.xul
@@ -0,0 +1,90 @@
+<?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"?>
+
+<!--
+ simple syntax enclosed in a container
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <hbox>
+ <label step="3" id="http://www.some-fictitious-zoo.com/birds/wren" value="Wren"/>
+ <label id="http://www.some-fictitious-zoo.com/birds/emu" value="Emu"/>
+ <label step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl" value="Barn Owl"/>
+ <label id="http://www.some-fictitious-zoo.com/birds/raven" value="Raven"/>
+ <label step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx" value="Archaeopteryx"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" value="Emperor Penguin"/>
+ </hbox>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple syntax enclosed in a container";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template id="template">
+<hbox>
+<label uri="rdf:*" value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</hbox>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxenclosedinacontainerwitharule.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxenclosedinacontainerwitharule.xul
new file mode 100644
index 000000000..5582e4845
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxenclosedinacontainerwitharule.xul
@@ -0,0 +1,58 @@
+<?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"?>
+
+<!--
+ simple syntax enclosed in a container with a rule
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <hbox id="http://www.some-fictitious-zoo.com/birds/emu">
+ <label value="Emu"/>
+ </hbox>
+ <hbox id="http://www.some-fictitious-zoo.com/birds/barnowl">
+ <label value="Barn Owl"/>
+ </hbox>
+ <hbox id="http://www.some-fictitious-zoo.com/birds/raven">
+ <label value="Raven"/>
+ </hbox>
+ <label step="2" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" value="Emperor Penguin"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple syntax enclosed in a container with a rule";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template id="template">
+<rule>
+<hbox uri="rdf:*">
+<label value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</hbox>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxfilter.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxfilter.xul
new file mode 100644
index 000000000..8715d3cc1
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxfilter.xul
@@ -0,0 +1,45 @@
+<?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"?>
+
+<!--
+ simple syntax - filter
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/birds/barnowl" value="Barn Owl"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple syntax - filter";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<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>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxfilterwithmultiplerules.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxfilterwithmultiplerules.xul
new file mode 100644
index 000000000..ba8591f60
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxfilterwithmultiplerules.xul
@@ -0,0 +1,97 @@
+<?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"?>
+
+<!--
+ simple syntax - filter with multiple rules
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <button id="http://www.some-fictitious-zoo.com/birds/emu" label="Emu"/>
+ <label step="-1" id="http://www.some-fictitious-zoo.com/birds/barnowl" value="Barn Owl"/>
+ <button step="4,-5" id="http://www.some-fictitious-zoo.com/birds/barnowl" label="Spooky Bird"/>
+ <label step="5" id="http://www.some-fictitious-zoo.com/birds/barnowl" value="Barn Owl"/>
+ <button step="1,-3" id="http://www.some-fictitious-zoo.com/birds/barnowl" label="Spooky Bird"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/raven" label="Raven"/>
+ <label step="2" id="http://www.some-fictitious-zoo.com/birds/syntheticbarnowl" value="Barn Owl"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple syntax - filter with multiple rules";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'birds/barnowl');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#name');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'birds/barnowl'),
+ RDF.GetResource(ZOO_NS + 'rdf#name'),
+ oldval, RDF.GetLiteral('Spooky Bird'));
+ },
+ // step 2
+ function(targetds, root) {
+ var newnode = RDF.GetResource(ZOO_NS + 'birds/syntheticbarnowl');
+ targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+ RDF.GetLiteral('Barn Owl'), true);
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'birds'));
+ container.AppendElement(newnode);
+ },
+ // step 3
+ function(targetds, root) {
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'birds'));
+ var removednode = container.RemoveElementAt('2', true);
+ },
+ // step 4
+ function(targetds, root) {
+ var newnode = RDF.GetResource(ZOO_NS + 'birds/barnowl');
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'birds'));
+ container.InsertElementAt(newnode, '2', true);
+ },
+ // step 5
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'birds/barnowl');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#name');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'birds/barnowl'),
+ RDF.GetResource(ZOO_NS + 'rdf#name'),
+ oldval, RDF.GetLiteral('Barn Owl'));
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template>
+<rule 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"/>
+</rule>
+<rule>
+<button uri="rdf:*" label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxfilterwithrule.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxfilterwithrule.xul
new file mode 100644
index 000000000..a2a3eb55e
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxfilterwithrule.xul
@@ -0,0 +1,98 @@
+<?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"?>
+
+<!--
+ simple syntax - filter with rule
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <hbox step="-1" id="http://www.some-fictitious-zoo.com/birds/barnowl">
+ <label value="Barn Owl"/>
+ </hbox>
+ <hbox step="5" id="http://www.some-fictitious-zoo.com/birds/barnowl">
+ <label value="Barn Owl"/>
+ </hbox>
+ <hbox step="2" id="http://www.some-fictitious-zoo.com/birds/syntheticbarnowl">
+ <label value="Barn Owl"/>
+ </hbox>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple syntax - filter with rule";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'birds/barnowl');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#name');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'birds/barnowl'),
+ RDF.GetResource(ZOO_NS + 'rdf#name'),
+ oldval, RDF.GetLiteral('Spooky Bird'));
+ },
+ // step 2
+ function(targetds, root) {
+ var newnode = RDF.GetResource(ZOO_NS + 'birds/syntheticbarnowl');
+ targetds.Assert(newnode, RDF.GetResource(ZOO_NS + 'rdf#name'),
+ RDF.GetLiteral('Barn Owl'), true);
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'birds'));
+ container.AppendElement(newnode);
+ },
+ // step 3
+ function(targetds, root) {
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'birds'));
+ var removednode = container.RemoveElementAt('2', true);
+ },
+ // step 4
+ function(targetds, root) {
+ var newnode = RDF.GetResource(ZOO_NS + 'birds/barnowl');
+ var container = ContainerUtils.MakeSeq(targetds,
+ RDF.GetResource(ZOO_NS + 'birds'));
+ container.InsertElementAt(newnode, '2', true);
+ },
+ // step 5
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'birds/barnowl');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#name');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'birds/barnowl'),
+ RDF.GetResource(ZOO_NS + 'rdf#name'),
+ oldval, RDF.GetLiteral('Barn Owl'));
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template>
+<rule zoo:name="Barn Owl" xmlns:zoo="http://www.some-fictitious-zoo.com/rdf#">
+<hbox uri="rdf:*">
+<label value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</hbox>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxiteratingoverasinglevalue.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxiteratingoverasinglevalue.xul
new file mode 100644
index 000000000..e8e05acf2
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxiteratingoverasinglevalue.xul
@@ -0,0 +1,86 @@
+<?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"?>
+
+<!--
+ simple syntax iterating over a single value
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="3" id="http://www.some-fictitious-zoo.com/birds/wren" value="Wren"/>
+ <label id="http://www.some-fictitious-zoo.com/birds/emu" value="Emu"/>
+ <label step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl" value="Barn Owl"/>
+ <label id="http://www.some-fictitious-zoo.com/birds/raven" value="Raven"/>
+ <label step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx" value="Archaeopteryx"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" value="Emperor Penguin"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple syntax iterating over a single value";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template id="template">
+<label uri="rdf:*" value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusinganinterveningcontainer.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusinganinterveningcontainer.xul
new file mode 100644
index 000000000..5aa5172c9
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusinganinterveningcontainer.xul
@@ -0,0 +1,90 @@
+<?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"?>
+
+<!--
+ simple syntax using an intervening container
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <groupbox>
+ <label step="3" id="http://www.some-fictitious-zoo.com/birds/wren" value="Wren"/>
+ <label id="http://www.some-fictitious-zoo.com/birds/emu" value="Emu"/>
+ <label step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl" value="Barn Owl"/>
+ <label id="http://www.some-fictitious-zoo.com/birds/raven" value="Raven"/>
+ <label step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx" value="Archaeopteryx"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" value="Emperor Penguin"/>
+ </groupbox>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple syntax using an intervening container";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template id="template">
+<groupbox>
+<label uri="rdf:*" value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</groupbox>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingatextnode.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingatextnode.xul
new file mode 100644
index 000000000..3fb1fdfa0
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingatextnode.xul
@@ -0,0 +1,50 @@
+<?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"?>
+
+<!--
+ simple syntax using a textnode
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <description step="3" id="http://www.some-fictitious-zoo.com/birds/wren">Wren</description>
+ <description id="http://www.some-fictitious-zoo.com/birds/emu">Emu</description>
+ <description step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl">Barn Owl</description>
+ <description id="http://www.some-fictitious-zoo.com/birds/raven">Raven</description>
+ <description step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx">Archaeopteryx</description>
+ <description step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin">Emperor Penguin</description>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple syntax using a textnode";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template id="template">
+<description uri="rdf:*"><textnode value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/></description>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingcontainerasthegenerationelement.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingcontainerasthegenerationelement.xul
new file mode 100644
index 000000000..238540fb4
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingcontainerasthegenerationelement.xul
@@ -0,0 +1,100 @@
+<?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"?>
+
+<!--
+ simple syntax using container as the generation element
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <hbox step="3" id="http://www.some-fictitious-zoo.com/birds/wren">
+ <label value="Wren"/>
+ </hbox>
+ <hbox id="http://www.some-fictitious-zoo.com/birds/emu">
+ <label value="Emu"/>
+ </hbox>
+ <hbox step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl">
+ <label value="Barn Owl"/>
+ </hbox>
+ <hbox id="http://www.some-fictitious-zoo.com/birds/raven">
+ <label value="Raven"/>
+ </hbox>
+ <hbox step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx">
+ <label value="Archaeopteryx"/>
+ </hbox>
+ <hbox step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin">
+ <label value="Emperor Penguin"/>
+ </hbox>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple syntax using container as the generation element";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template id="template">
+<hbox uri="rdf:*">
+<label value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</hbox>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingdontrecurse.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingdontrecurse.xul
new file mode 100644
index 000000000..31aa9c404
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingdontrecurse.xul
@@ -0,0 +1,52 @@
+<?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"?>
+
+<!--
+ simple syntax using dont-recurse
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <hbox id="http://www.some-fictitious-zoo.com/arachnids" container="true" empty="false">
+ <label value="Arachnids"/>
+ </hbox>
+ <hbox id="http://www.some-fictitious-zoo.com/birds" container="true" empty="false">
+ <label value="Birds"/>
+ </hbox>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple syntax using dont-recurse";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/some-animals" flags="dont-recurse">
+<template id="template">
+<hbox uri="rdf:*">
+<label value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</hbox>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingrecursivegeneration.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingrecursivegeneration.xul
new file mode 100644
index 000000000..3ef3b1d3d
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingrecursivegeneration.xul
@@ -0,0 +1,109 @@
+<?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"?>
+
+<!--
+ simple syntax using recursive generation
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <hbox id="http://www.some-fictitious-zoo.com/arachnids" container="true" empty="false">
+ <label value="Arachnids"/>
+ <hbox id="http://www.some-fictitious-zoo.com/arachnids/tarantula">
+ <label value="Tarantula"/>
+ </hbox>
+ </hbox>
+ <hbox id="http://www.some-fictitious-zoo.com/birds" container="true" empty="false">
+ <label value="Birds"/>
+ <hbox step="3" id="http://www.some-fictitious-zoo.com/birds/wren">
+ <label value="Wren"/>
+ </hbox>
+ <hbox id="http://www.some-fictitious-zoo.com/birds/emu">
+ <label value="Emu"/>
+ </hbox>
+ <hbox step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl">
+ <label value="Barn Owl"/>
+ </hbox>
+ <hbox id="http://www.some-fictitious-zoo.com/birds/raven">
+ <label value="Raven"/>
+ </hbox>
+ <hbox step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx">
+ <label value="Archaeopteryx"/>
+ </hbox>
+ <hbox step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin">
+ <label value="Emperor Penguin"/>
+ </hbox>
+ </hbox>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple syntax using recursive generation";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/some-animals">
+<template id="template">
+<hbox uri="rdf:*">
+<label value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</hbox>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingrecursivegenerationagain.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingrecursivegenerationagain.xul
new file mode 100644
index 000000000..73d7f742e
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxusingrecursivegenerationagain.xul
@@ -0,0 +1,64 @@
+<?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"?>
+
+<!--
+ simple syntax using recursive generation again
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <hbox id="http://www.some-fictitious-zoo.com/arachnids" container="true" empty="false">
+ <label value="Arachnids"/>
+ <hbox id="http://www.some-fictitious-zoo.com/arachnids/tarantula">
+ <label value="Tarantula"/>
+ </hbox>
+ </hbox>
+ <hbox id="http://www.some-fictitious-zoo.com/birds" container="true" empty="false">
+ <label value="Birds"/>
+ <hbox id="http://www.some-fictitious-zoo.com/birds/emu">
+ <label value="Emu"/>
+ </hbox>
+ <hbox id="http://www.some-fictitious-zoo.com/birds/barnowl">
+ <label value="Barn Owl"/>
+ </hbox>
+ <hbox id="http://www.some-fictitious-zoo.com/birds/raven">
+ <label value="Raven"/>
+ </hbox>
+ </hbox>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple syntax using recursive generation again";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/some-animals">
+<template id="template">
+<hbox uri="rdf:*">
+<label value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</hbox>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxwithtwovariablesused.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxwithtwovariablesused.xul
new file mode 100644
index 000000000..fccee70ce
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplesyntaxwithtwovariablesused.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ simple syntax with two variables used
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <hbox id="http://www.some-fictitious-zoo.com/birds/emu">
+ <label value="Emu"/>
+ <label value="12"/>
+ </hbox>
+ <hbox id="http://www.some-fictitious-zoo.com/birds/barnowl">
+ <label value="Barn Owl"/>
+ <label value="4"/>
+ </hbox>
+ <hbox id="http://www.some-fictitious-zoo.com/birds/raven">
+ <label value="Raven"/>
+ <label value="0"/>
+ </hbox>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple syntax with two variables used";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template id="template">
+<hbox uri="rdf:*">
+<label value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+<label value="rdf:http://www.some-fictitious-zoo.com/rdf#specimens"/>
+</hbox>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutioncaretsatbeginningandend.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutioncaretsatbeginningandend.xul
new file mode 100644
index 000000000..2ca63da19
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutioncaretsatbeginningandend.xul
@@ -0,0 +1,45 @@
+<?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"?>
+
+<!--
+ simple variable substitution - carets at beginning and end
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="^^Before Tarantula^"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple variable substitution - carets at beginning and end";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/arachnids">
+<template id="template">
+<label uri="rdf:*" value="^^Before rdf:http://www.some-fictitious-zoo.com/rdf#name^^"/>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutioncaretsubstitution.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutioncaretsubstitution.xul
new file mode 100644
index 000000000..abc28ff0f
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutioncaretsubstitution.xul
@@ -0,0 +1,45 @@
+<?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"?>
+
+<!--
+ simple variable substitution - caret substitution
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Tarantula^ and^^ lots ^ more"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple variable substitution - caret substitution";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/arachnids">
+<template id="template">
+<label uri="rdf:*" value="rdf:http://www.some-fictitious-zoo.com/rdf#name^^ and^^ lots ^ more"/>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionnovariable.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionnovariable.xul
new file mode 100644
index 000000000..4d2404aca
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionnovariable.xul
@@ -0,0 +1,45 @@
+<?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"?>
+
+<!--
+ simple variable substitution - no variable
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Name"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple variable substitution - no variable";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/arachnids">
+<template id="template">
+<label uri="rdf:*" value="Name"/>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionquestionmarkaspartofvariable.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionquestionmarkaspartofvariable.xul
new file mode 100644
index 000000000..0dd1a7ccf
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionquestionmarkaspartofvariable.xul
@@ -0,0 +1,45 @@
+<?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"?>
+
+<!--
+ simple variable substitution - question mark as part of variable
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Name is "/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple variable substitution - question mark as part of variable";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/arachnids">
+<template id="template">
+<label uri="rdf:*" value="Name is rdf:http://www.some-fictitious-zoo.com/rdf#name?"/>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionquestionmarksubstitution.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionquestionmarksubstitution.xul
new file mode 100644
index 000000000..c31401dd4
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionquestionmarksubstitution.xul
@@ -0,0 +1,56 @@
+<?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"?>
+
+<!--
+ simple variable substitution - question mark substitution
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="-1" id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Are you a Tarantula ?"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Are you a Hairy Spider ?"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple variable substitution - question mark substitution";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'arachnids/tarantula');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#name');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'arachnids/tarantula'),
+ RDF.GetResource(ZOO_NS + 'rdf#name'),
+ oldval, RDF.GetLiteral('Hairy Spider'));
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/arachnids">
+<template id="template">
+<label uri="rdf:*" value="Are you a rdf:http://www.some-fictitious-zoo.com/rdf#name ?sample ??"/>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutiontextandvariable.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutiontextandvariable.xul
new file mode 100644
index 000000000..6d2bc806b
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutiontextandvariable.xul
@@ -0,0 +1,56 @@
+<?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"?>
+
+<!--
+ simple variable substitution - text and variable
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="-1" id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Name is Tarantula the Spider"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Name is Hairy Spider the Spider"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple variable substitution - text and variable";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'arachnids/tarantula');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#name');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'arachnids/tarantula'),
+ RDF.GetResource(ZOO_NS + 'rdf#name'),
+ oldval, RDF.GetLiteral('Hairy Spider'));
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/arachnids">
+<template id="template">
+<label uri="rdf:*" value="Name is rdf:http://www.some-fictitious-zoo.com/rdf#name the Spider"/>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionvariableandtextconcatenated.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionvariableandtextconcatenated.xul
new file mode 100644
index 000000000..41df3d4d8
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionvariableandtextconcatenated.xul
@@ -0,0 +1,45 @@
+<?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"?>
+
+<!--
+ simple variable substitution - variable and text concatenated
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="The Tarantula's Nest"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple variable substitution - variable and text concatenated";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/arachnids">
+<template id="template">
+<label uri="rdf:*" value="The rdf:http://www.some-fictitious-zoo.com/rdf#name^'s Nest"/>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionvariablesconcatenated.xul b/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionvariablesconcatenated.xul
new file mode 100644
index 000000000..b8864c3b3
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_simplevariablesubstitutionvariablesconcatenated.xul
@@ -0,0 +1,45 @@
+<?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"?>
+
+<!--
+ simple variable substitution - variables concatenated
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Tarantula3"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="simple variable substitution - variables concatenated";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/arachnids">
+<template id="template">
+<label uri="rdf:*" value="rdf:http://www.some-fictitious-zoo.com/rdf#name^rdf:http://www.some-fictitious-zoo.com/rdf#specimens"/>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sortascendinginteger.xul b/dom/xul/templates/tests/chrome/test_tmpl_sortascendinginteger.xul
new file mode 100644
index 000000000..2d30ced27
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sortascendinginteger.xul
@@ -0,0 +1,70 @@
+<?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"?>
+
+<!--
+ sort ascending - integers
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label step="-1" id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sort ascending - integers";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'mammals/llama');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#specimensAsString');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(subject, predicate, oldval, RDF.GetLiteral('12'), true);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null"
+ ref="http://www.some-fictitious-zoo.com/mammals" sort="?specimens ?name" sortDirection="ascending" sorthints="integer">
+<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"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#specimensAsString" object="?specimens"/>
+</query>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sortascendingquerysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_sortascendingquerysyntax.xul
new file mode 100644
index 000000000..5f90a8334
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sortascendingquerysyntax.xul
@@ -0,0 +1,114 @@
+<?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"?>
+
+<!--
+ sort ascending - query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label step="-4" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label step="5" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/mammals/arctichare" value="Arctic Hare"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label step="2" id="http://www.some-fictitious-zoo.com/mammals/koala" value="Koala"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label step="3" id="http://www.some-fictitious-zoo.com/mammals/zebra" value="Zebra"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sort ascending - query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals" sort="?name" sortDirection="ascending">
+<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>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sortascendingtworulesquerysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_sortascendingtworulesquerysyntax.xul
new file mode 100644
index 000000000..612a50fc6
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sortascendingtworulesquerysyntax.xul
@@ -0,0 +1,70 @@
+<?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"?>
+
+<!--
+ sort ascending two rules - query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/aardvark" label="aardvark"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" label="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/llama" label="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/africanelephant" label="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sort ascending two rules - query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals" sortDirection="ascending" sort="?specimens ?name" sortResource="http://www.some-fictitious-zoo.com/rdf#specimens" sortResource2="http://www.some-fictitious-zoo.com/rdf#name">
+<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"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#specimens" object="?specimens"/>
+</query>
+<rule>
+<conditions>
+<where subject="?name" rel="contains" value="o"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+<rule>
+<action>
+<button uri="?animal" label="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sortascendingtworuleswithcontainerquerysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_sortascendingtworuleswithcontainerquerysyntax.xul
new file mode 100644
index 000000000..e3fb2e37b
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sortascendingtworuleswithcontainerquerysyntax.xul
@@ -0,0 +1,80 @@
+<?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"?>
+
+<!--
+ sort ascending two rules with container - query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <hbox id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo">
+ <label value="Nine-banded Armadillo"/>
+ </hbox>
+ <button id="http://www.some-fictitious-zoo.com/mammals/aardvark" label="aardvark"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" label="HIPPOPOTAMUS"/>
+ <hbox id="http://www.some-fictitious-zoo.com/mammals/lion">
+ <label value="Lion"/>
+ </hbox>
+ <button id="http://www.some-fictitious-zoo.com/mammals/llama" label="LLAMA"/>
+ <hbox id="http://www.some-fictitious-zoo.com/mammals/gorilla">
+ <label value="Gorilla"/>
+ </hbox>
+ <button id="http://www.some-fictitious-zoo.com/mammals/africanelephant" label="African Elephant"/>
+ <hbox id="http://www.some-fictitious-zoo.com/mammals/polarbear">
+ <label value="Polar Bear"/>
+ </hbox>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sort ascending two rules with container - query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals" sortDirection="ascending" sort="?specimens ?name" sortResource="http://www.some-fictitious-zoo.com/rdf#specimens" sortResource2="http://www.some-fictitious-zoo.com/rdf#name">
+<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"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#specimens" object="?specimens"/>
+</query>
+<rule>
+<conditions>
+<where subject="?name" rel="contains" value="o"/>
+</conditions>
+<action id="action">
+<hbox uri="?animal">
+<label value="?name"/>
+</hbox>
+</action>
+</rule>
+<rule>
+<action>
+<button uri="?animal" label="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sortascendingtworuleswithdifferentcontainerquerysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_sortascendingtworuleswithdifferentcontainerquerysyntax.xul
new file mode 100644
index 000000000..fdab29054
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sortascendingtworuleswithdifferentcontainerquerysyntax.xul
@@ -0,0 +1,84 @@
+<?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"?>
+
+<!--
+ sort ascending two rules with different container - query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <vbox>
+ <hbox id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo">
+ <label value="Nine-banded Armadillo"/>
+ </hbox>
+ <hbox id="http://www.some-fictitious-zoo.com/mammals/lion">
+ <label value="Lion"/>
+ </hbox>
+ <hbox id="http://www.some-fictitious-zoo.com/mammals/gorilla">
+ <label value="Gorilla"/>
+ </hbox>
+ <hbox id="http://www.some-fictitious-zoo.com/mammals/polarbear">
+ <label value="Polar Bear"/>
+ </hbox>
+ </vbox>
+ <button id="http://www.some-fictitious-zoo.com/mammals/aardvark" label="aardvark"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" label="HIPPOPOTAMUS"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/llama" label="LLAMA"/>
+ <button id="http://www.some-fictitious-zoo.com/mammals/africanelephant" label="African Elephant"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sort ascending two rules with different container - query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals" sortDirection="ascending" sort="?specimens ?name" sortResource="http://www.some-fictitious-zoo.com/rdf#specimens" sortResource2="http://www.some-fictitious-zoo.com/rdf#name">
+<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"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#specimens" object="?specimens"/>
+</query>
+<rule>
+<conditions>
+<where subject="?name" rel="contains" value="o"/>
+</conditions>
+<action id="action">
+<vbox>
+<hbox uri="?animal">
+<label value="?name"/>
+</hbox>
+</vbox>
+</action>
+</rule>
+<rule>
+<action>
+<button uri="?animal" label="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sortdescendingquerysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_sortdescendingquerysyntax.xul
new file mode 100644
index 000000000..1979fdb98
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sortdescendingquerysyntax.xul
@@ -0,0 +1,114 @@
+<?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"?>
+
+<!--
+ sort descending - query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="3" id="http://www.some-fictitious-zoo.com/mammals/zebra" value="Zebra"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label step="2" id="http://www.some-fictitious-zoo.com/mammals/koala" value="Koala"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/mammals/arctichare" value="Arctic Hare"/>
+ <label step="5" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label step="-4" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sort descending - query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals" sort="?name" sortDirection="descending">
+<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>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sortquerymemberandtwotriples.xul b/dom/xul/templates/tests/chrome/test_tmpl_sortquerymemberandtwotriples.xul
new file mode 100644
index 000000000..8176553e4
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sortquerymemberandtwotriples.xul
@@ -0,0 +1,58 @@
+<?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"?>
+
+<!--
+ sort query - member and two triples
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/reptiles/anaconda" value="Anaconda"/>
+ <label id="http://www.some-fictitious-zoo.com/reptiles/chameleon" value="Chameleon"/>
+ <label id="http://www.some-fictitious-zoo.com/birds/emu" value="Emu"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/arachnids/tarantula" value="Tarantula"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sort query - member and two triples";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/humans" sortDirection="ascending" sort="?name" sortResource="http://www.some-fictitious-zoo.com/rdf#specimens" sortResource2="http://www.some-fictitious-zoo.com/rdf#name">
+<template id="template">
+<query id="query">
+<content uri="?uri"/>
+<member container="?uri" child="?human"/>
+<triple subject="?human" predicate="http://www.some-fictitious-zoo.com/rdf#favoriteAnimal" object="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<action>
+<label uri="?child" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sortresource2descendingsimplesyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_sortresource2descendingsimplesyntax.xul
new file mode 100644
index 000000000..d328d81b2
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sortresource2descendingsimplesyntax.xul
@@ -0,0 +1,52 @@
+<?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"?>
+
+<!--
+ sortResource2 descending - simple syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sortResource2 descending - simple syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals" sortDirection="descending" sort="http://www.some-fictitious-zoo.com/rdf#specimens http://www.some-fictitious-zoo.com/rdf#name" sortResource="http://www.some-fictitious-zoo.com/rdf#specimens" sortResource2="http://www.some-fictitious-zoo.com/rdf#name">
+<template id="template">
+<label uri="rdf:*" value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sortresource2settopredicateascendingquerysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_sortresource2settopredicateascendingquerysyntax.xul
new file mode 100644
index 000000000..653584c52
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sortresource2settopredicateascendingquerysyntax.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ sortResource2 set to predicate ascending - query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sortResource2 set to predicate ascending - query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals" sortResource="http://www.some-fictitious-zoo.com/rdf#specimens" sortDirection="ascending" sortResource2="http://www.some-fictitious-zoo.com/rdf#name">
+<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>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sortresource2settopredicatedescendingquerysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_sortresource2settopredicatedescendingquerysyntax.xul
new file mode 100644
index 000000000..8fcb569a7
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sortresource2settopredicatedescendingquerysyntax.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ sortResource2 set to predicate descending - query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sortResource2 set to predicate descending - query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals" sortResource="http://www.some-fictitious-zoo.com/rdf#specimens" sortDirection="descending" sortResource2="http://www.some-fictitious-zoo.com/rdf#name">
+<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>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sortresourceascendingquerysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_sortresourceascendingquerysyntax.xul
new file mode 100644
index 000000000..346a988a5
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sortresourceascendingquerysyntax.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ sortResource ascending - query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sortResource ascending - query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals" sortResource="?name" sortDirection="ascending">
+<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>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sortresourcedescendingquerysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_sortresourcedescendingquerysyntax.xul
new file mode 100644
index 000000000..7281eb6ef
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sortresourcedescendingquerysyntax.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ sortResource descending - query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sortResource descending - query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals" sortResource="?name" sortDirection="descending">
+<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>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sortresourcesettopredicateascendingquerysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_sortresourcesettopredicateascendingquerysyntax.xul
new file mode 100644
index 000000000..151c5d5ae
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sortresourcesettopredicateascendingquerysyntax.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ sortResource set to predicate ascending - query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sortResource set to predicate ascending - query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals" sortResource="http://www.some-fictitious-zoo.com/rdf#specimens" sortDirection="ascending">
+<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>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sortresourcesettopredicatedescendingquerysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_sortresourcesettopredicatedescendingquerysyntax.xul
new file mode 100644
index 000000000..893f056b8
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sortresourcesettopredicatedescendingquerysyntax.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ sortResource set to predicate descending - query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sortResource set to predicate descending - query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals" sortResource="http://www.some-fictitious-zoo.com/rdf#specimens" sortDirection="descending">
+<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>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sorttworesourcesasstringsettopredicatedescendingquerysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_sorttworesourcesasstringsettopredicatedescendingquerysyntax.xul
new file mode 100644
index 000000000..1daf2dab9
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sorttworesourcesasstringsettopredicatedescendingquerysyntax.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ sort two resources as string set to predicate descending - query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sort two resources as string set to predicate descending - query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals" sortDirection="descending" sort="http://www.some-fictitious-zoo.com/rdf#specimensAsString http://www.some-fictitious-zoo.com/rdf#name">
+<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>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sorttworesourcessettopredicateascendingquerysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_sorttworesourcessettopredicateascendingquerysyntax.xul
new file mode 100644
index 000000000..8d9137ffa
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sorttworesourcessettopredicateascendingquerysyntax.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ sort two resources set to predicate ascending - query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sort two resources set to predicate ascending - query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals" sortDirection="ascending" sort="http://www.some-fictitious-zoo.com/rdf#specimens http://www.some-fictitious-zoo.com/rdf#name">
+<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>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sorttwovariablesascendingquerysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_sorttwovariablesascendingquerysyntax.xul
new file mode 100644
index 000000000..6a396d3b9
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sorttwovariablesascendingquerysyntax.xul
@@ -0,0 +1,119 @@
+<?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"?>
+
+<!--
+ sort two variables ascending - query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <label step="5" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label step="-4" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/mammals/arctichare" value="Arctic Hare"/>
+ <label step="2" id="http://www.some-fictitious-zoo.com/mammals/koala" value="Koala"/>
+ <label step="3" id="http://www.some-fictitious-zoo.com/mammals/zebra" value="Zebra"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sort two variables ascending - query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = true;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals" sortDirection="ascending" sort="?specimens ?name">
+<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>
+<bindings>
+<binding subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#specimens" object="?specimens"/>
+</bindings>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sorttwovariablesascendingsimplesyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_sorttwovariablesascendingsimplesyntax.xul
new file mode 100644
index 000000000..fd8200a66
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sorttwovariablesascendingsimplesyntax.xul
@@ -0,0 +1,52 @@
+<?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"?>
+
+<!--
+ sort two variables ascending - simple syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sort two variables ascending - simple syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals" sortDirection="ascending" sort="http://www.some-fictitious-zoo.com/rdf#specimens http://www.some-fictitious-zoo.com/rdf#name">
+<template id="template">
+<label uri="rdf:*" value="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sorttwovariablesdescendingquerysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_sorttwovariablesdescendingquerysyntax.xul
new file mode 100644
index 000000000..f2fb6ca9d
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sorttwovariablesdescendingquerysyntax.xul
@@ -0,0 +1,64 @@
+<?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"?>
+
+<!--
+ sort two variables descending - query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sort two variables descending - query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals" sortDirection="descending" sort="?specimens ?name">
+<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>
+<bindings>
+<binding subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#specimens" object="?specimens"/>
+</bindings>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_sortunknownascendingquerysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_sortunknownascendingquerysyntax.xul
new file mode 100644
index 000000000..d995c4c1c
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_sortunknownascendingquerysyntax.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ sort unknown ascending - query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sort unknown ascending - query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/mammals" sort="?other">
+<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>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_storage_bad_parameters.xul b/dom/xul/templates/tests/chrome/test_tmpl_storage_bad_parameters.xul
new file mode 100644
index 000000000..b11367e28
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_storage_bad_parameters.xul
@@ -0,0 +1,58 @@
+<?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"?>
+
+<!--
+ storage listbox with bad query parameters
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output"/>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="storage listbox with bad query parameters";
+var queryType = "storage";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+
+Components.classes["@mozilla.org/consoleservice;1"]
+ .getService(Components.interfaces.nsIConsoleService)
+ .reset();
+
+copyToProfile('animals.sqlite');
+expectedConsoleMessages.push("Error parsing template: the given named parameter is unknown in the SQL query");
+
+var changes = [];
+]]>
+</script>
+
+<listbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root"
+ flex="1" datasources="profile:animals.sqlite" ref="." querytype="storage">
+ <template>
+ <query>
+ SELECT * FROM animals WHERE species_id = ? ORDER BY name
+ <param name="bar">2</param>
+ </query>
+ <action>
+ <listitem uri="?" label="?name"/>
+ </action>
+ </template>
+</listbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_storage_bad_parameters_2.xul b/dom/xul/templates/tests/chrome/test_tmpl_storage_bad_parameters_2.xul
new file mode 100644
index 000000000..81517d2df
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_storage_bad_parameters_2.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ storage listbox with bad query parameters
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output"/>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="storage listbox with bad query parameters";
+var queryType = "storage";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+
+Components.classes["@mozilla.org/consoleservice;1"]
+ .getService(Components.interfaces.nsIConsoleService)
+ .reset();
+
+copyToProfile('animals.sqlite');
+
+expectedConsoleMessages.push("Error parsing template: the type of a query parameter is wrong");
+
+var changes = [];
+]]>
+</script>
+
+<listbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root"
+ flex="1" datasources="profile:animals.sqlite" ref="." querytype="storage">
+ <template>
+ <query>
+ SELECT * FROM animals WHERE species_id = ? ORDER BY name
+ <param type="mysupertype">2</param>
+ </query>
+ <action>
+ <listitem uri="?" label="?name"/>
+ </action>
+ </template>
+</listbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_storage_bad_parameters_3.xul b/dom/xul/templates/tests/chrome/test_tmpl_storage_bad_parameters_3.xul
new file mode 100644
index 000000000..22be02c28
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_storage_bad_parameters_3.xul
@@ -0,0 +1,60 @@
+<?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"?>
+
+<!--
+ storage listbox with bad query parameters
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output"/>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="storage listbox with bad query parameters";
+var queryType = "storage";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+
+Components.classes["@mozilla.org/consoleservice;1"]
+ .getService(Components.interfaces.nsIConsoleService)
+ .reset();
+
+copyToProfile('animals.sqlite');
+
+expectedConsoleMessages.push("Error parsing template: a query parameter cannot be bound to the SQL query");
+
+var changes = [];
+]]>
+</script>
+
+<listbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root"
+ flex="1" datasources="profile:animals.sqlite" ref="." querytype="storage">
+ <template>
+ <query>
+ SELECT * FROM animals WHERE species_id = :spec ORDER BY name
+ <param name="spec" type="int32">5</param>
+ <param>L%</param>
+ </query>
+ <action>
+ <listitem uri="?" label="?name"/>
+ </action>
+ </template>
+</listbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_storage_baddatasource.xul b/dom/xul/templates/tests/chrome/test_tmpl_storage_baddatasource.xul
new file mode 100644
index 000000000..da9c83d0c
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_storage_baddatasource.xul
@@ -0,0 +1,56 @@
+<?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"?>
+
+<!--
+ storage bad datasource
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output"/>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="storage bad datasource";
+var queryType = "storage";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+Components.classes["@mozilla.org/consoleservice;1"]
+ .getService(Components.interfaces.nsIConsoleService)
+ .reset();
+
+expectedConsoleMessages.push("Error parsing template: only profile: or file URI are allowed");
+
+
+var changes = [];
+]]>
+</script>
+
+<listbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root"
+ flex="1" rows="8"
+ datasources="http://foo.local/animals.sqlite" ref="." querytype="storage">
+ <template>
+ <query>
+ SELECT * FROM animals WHERE species_id = 2 ORDER BY name
+ </query>
+ <action>
+ <listitem uri="?" label="?name" />
+ </action>
+ </template>
+</listbox>
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_storage_badquery.xul b/dom/xul/templates/tests/chrome/test_tmpl_storage_badquery.xul
new file mode 100644
index 000000000..39df0edac
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_storage_badquery.xul
@@ -0,0 +1,58 @@
+<?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"?>
+
+<!--
+ storage bad query
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output"/>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="storage bad query";
+var queryType = "storage";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+Components.classes["@mozilla.org/consoleservice;1"]
+ .getService(Components.interfaces.nsIConsoleService)
+ .reset();
+
+copyToProfile('animals.sqlite');
+
+expectedConsoleMessages.push("Error parsing template: syntax error in the SQL query");
+
+
+var changes = [];
+]]>
+</script>
+
+<listbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root"
+ flex="1" rows="8"
+ datasources="profile:animals.sqlite" ref="." querytype="storage">
+ <template>
+ <query>
+ SELECT * FROM animalssssssss WHERE species_id = 2 ORDER BY
+ </query>
+ <action>
+ <listitem uri="?" label="?name" />
+ </action>
+ </template>
+</listbox>
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_storage_dynamicparameters.xul b/dom/xul/templates/tests/chrome/test_tmpl_storage_dynamicparameters.xul
new file mode 100644
index 000000000..c791c6acd
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_storage_dynamicparameters.xul
@@ -0,0 +1,84 @@
+<?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"?>
+
+<!--
+ storage listbox with dynamic query parameters
+-->
+
+<window title="XUL Template Tests" width="500" height="600"
+ onload="test_storage_template();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <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;"/>
+
+ <data id="output"/>
+
+ <data id="rebuilt-output">
+ <listitem anyid="true" label="Barn Owl"/>
+ <listitem anyid="true" label="Emu"/>
+ <listitem anyid="true" label="Raven"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+copyToProfile('animals.sqlite');
+
+function test_storage_template()
+{
+ var root = document.getElementById("root");
+ expectedOutput = document.getElementById("output");
+ checkResults(root, 0);
+
+ document.getElementById("species-id").textContent = '2';
+ root.builder.addListener(myBuilderListener);
+ root.builder.rebuild();
+}
+
+var myBuilderListener = {
+
+ willRebuild: function(aBuilder) {
+
+ },
+ didRebuild: function (aBuilder) {
+ var root = document.getElementById("root");
+ expectedOutput = document.getElementById("rebuilt-output");
+ checkResults(root, 0);
+ SimpleTest.finish();
+ }
+}
+
+var testid ="storage listbox with dynamic query parameters";
+var queryType = "storage";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<listbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root"
+ flex="1" datasources="profile:animals.sqlite" ref="." querytype="storage">
+ <template>
+ <query>
+ SELECT * FROM animals WHERE species_id = :spec ORDER BY name
+ <param id="species-id" name="spec" type="int32" />
+ </query>
+ <action>
+ <listitem uri="?" label="?name"/>
+ </action>
+ </template>
+</listbox>
+
+
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_storage_listbox.xul b/dom/xul/templates/tests/chrome/test_tmpl_storage_listbox.xul
new file mode 100644
index 000000000..43661c417
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_storage_listbox.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ storage simple with listbox
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <listitem anyid="true" label="Barn Owl"/>
+ <listitem anyid="true" label="Emu"/>
+ <listitem anyid="true" label="Raven"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+copyToProfile('animals.sqlite');
+
+var testid ="storage simple listbox";
+var queryType = "storage";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<listbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root"
+ flex="1" rows="8"
+ datasources="profile:animals.sqlite" ref="." querytype="storage">
+ <template>
+ <query>
+ SELECT * FROM animals WHERE species_id = 2 ORDER BY name
+ </query>
+ <action>
+ <listitem uri="?" label="?name" />
+ </action>
+ </template>
+</listbox>
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_storage_multiqueries.xul b/dom/xul/templates/tests/chrome/test_tmpl_storage_multiqueries.xul
new file mode 100644
index 000000000..28dcaa926
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_storage_multiqueries.xul
@@ -0,0 +1,86 @@
+<?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"?>
+
+<!--
+ storage listbox with multiqueries
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <listitem anyid="true" label="Mammal: African Elephant"/>
+ <listitem anyid="true" label="Mammal: Gorilla" style="font-weight:bold"/>
+ <listitem anyid="true" label="Mammal: HIPPOPOTAMUS"/>
+ <listitem anyid="true" label="Mammal: LAMA"/>
+ <listitem anyid="true" label="Mammal: Lion"/>
+ <listitem anyid="true" label="Mammal: Nine-banded Armadillo" style="font-weight:bold"/>
+ <listitem anyid="true" label="Mammal: Polar Bear"/>
+ <listitem anyid="true" label="Mammal: aardvark"/>
+ <listitem anyid="true" label="Bird: Barn Owl" style="font-style:italic"/>
+ <listitem anyid="true" label="Bird: Emu"/>
+ <listitem anyid="true" label="Bird: Raven"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+copyToProfile('animals.sqlite');
+
+var testid ="storage listbox with multiqueries";
+var queryType = "storage";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<listbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root"
+ flex="1" datasources="profile:animals.sqlite" ref="." querytype="storage">
+ <template>
+ <queryset>
+ <query>SELECT * FROM animals WHERE species_id = 5 ORDER BY name</query>
+ <rule>
+ <where subject="?id" rel="greater" value="12"/>
+ <action>
+ <listitem uri="?" label="Mammal: ?name" style="font-weight:bold"/>
+ </action>
+ </rule>
+ <rule>
+ <action>
+ <listitem uri="?" label="Mammal: ?name"/>
+ </action>
+ </rule>
+ </queryset>
+ <queryset>
+ <!-- we use aliases on columns just to have different "column names" in the result set
+ to "similate" a result set from another table for example -->
+ <query>SELECT * FROM animals WHERE species_id = 2 ORDER BY name</query>
+ <rule>
+ <where subject="?id" rel="equals" value="3"/>
+ <action>
+ <listitem uri="?" label="Bird: ?name" style="font-style:italic"/>
+ </action>
+ </rule>
+ <rule>
+ <action>
+ <listitem uri="?" label="Bird: ?name"/>
+ </action>
+ </rule>
+ </queryset>
+ </template>
+</listbox>
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_storage_parameters.xul b/dom/xul/templates/tests/chrome/test_tmpl_storage_parameters.xul
new file mode 100644
index 000000000..0df561884
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_storage_parameters.xul
@@ -0,0 +1,160 @@
+<?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"?>
+
+<!--
+ storage listbox with query parameters
+-->
+
+<window title="XUL Template Tests" width="500" height="600"
+ onload="test_storage_template();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <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;"/>
+
+ <data id="output-birds">
+ <listitem anyid="true" label="Barn Owl"/>
+ <listitem anyid="true" label="Emu"/>
+ <listitem anyid="true" label="Raven"/>
+ </data>
+
+ <data id="output-L">
+ <listitem anyid="true" label="LAMA"/>
+ <listitem anyid="true" label="Lion"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+
+copyToProfile('animals.sqlite');
+SimpleTest.waitForExplicitFinish();
+
+
+function test_storage_template()
+{
+ var root = document.getElementById("root1");
+ expectedOutput = document.getElementById("output-birds");
+ checkResults(root, 0);
+
+root = document.getElementById("root2");
+checkResults(root, 0);
+
+root = document.getElementById("root6");
+checkResults(root, 0);
+
+root = document.getElementById("root3");
+expectedOutput = document.getElementById("output-L");
+checkResults(root, 0);
+
+root = document.getElementById("root4");
+checkResults(root, 0);
+
+root = document.getElementById("root5");
+checkResults(root, 0);
+
+SimpleTest.finish();
+}
+
+
+var testid ="storage listbox with query parameters";
+var queryType = "storage";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput;
+
+var changes = [];
+]]>
+</script>
+
+<listbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root1"
+ flex="1" datasources="profile:animals.sqlite" ref="." querytype="storage">
+ <template>
+ <query>
+ SELECT * FROM animals WHERE species_id = ? ORDER BY name
+ <param>2</param>
+ </query>
+ <action>
+ <listitem uri="?" label="?name"/>
+ </action>
+ </template>
+</listbox>
+
+
+<listbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root2"
+ flex="1" datasources="profile:animals.sqlite" ref="." querytype="storage">
+ <template>
+ <query>
+ SELECT * FROM animals WHERE species_id = ? ORDER BY name
+ <param type="int32">2</param>
+ </query>
+ <action>
+ <listitem uri="?" label="?name"/>
+ </action>
+ </template>
+</listbox>
+
+
+<listbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root3"
+ flex="1" datasources="profile:animals.sqlite" ref="." querytype="storage">
+ <template>
+ <query>
+ SELECT * FROM animals WHERE species_id = :spec AND name like ? ORDER BY name
+ <param name="spec" type="int32">5</param>
+ <param>L%</param>
+ </query>
+ <action>
+ <listitem uri="?" label="?name"/>
+ </action>
+ </template>
+</listbox>
+
+<listbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root4"
+ flex="1" datasources="profile:animals.sqlite" ref="." querytype="storage">
+ <template>
+ <query>
+ SELECT * FROM animals WHERE species_id = ?3 AND name like ?1 ORDER BY name
+ <param index="3" type="int32">5</param>
+ <param index="1">L%</param>
+ </query>
+ <action>
+ <listitem uri="?" label="?name"/>
+ </action>
+ </template>
+</listbox>
+
+<listbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root5"
+ flex="1" datasources="profile:animals.sqlite" ref="." querytype="storage">
+ <template>
+ <query>
+ SELECT * FROM animals WHERE species_id = ?3 AND name like :pattern ORDER BY name
+ <param name="pattern">L%</param>
+ <param index="3" type="int32">5</param>
+ </query>
+ <action>
+ <listitem uri="?" label="?name"/>
+ </action>
+ </template>
+</listbox>
+
+<listbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root6"
+ flex="1" datasources="profile:animals.sqlite" ref="." querytype="storage">
+ <template>
+ <query>
+ SELECT * FROM animals WHERE species_id = ? ORDER BY name
+ <param type="int32">2qsdqsd</param>
+ </query>
+ <action>
+ <listitem uri="?" label="?name"/>
+ </action>
+ </template>
+</listbox>
+
+
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_storage_rule.xul b/dom/xul/templates/tests/chrome/test_tmpl_storage_rule.xul
new file mode 100644
index 000000000..003682750
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_storage_rule.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ storage listbox with rule
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <listitem anyid="true" label="Gorilla"/>
+ <listitem anyid="true" label="Nine-banded Armadillo"/>
+ <listitem anyid="true" label="Polar Bear"/>
+ <listitem anyid="true" label="aardvark"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+copyToProfile('animals.sqlite');
+
+var testid ="storage listbox with rule";
+var queryType = "storage";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<listbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root"
+ flex="1" rows="8"
+ datasources="profile:animals.sqlite" ref="." querytype="storage">
+ <template>
+ <query>
+ SELECT * FROM animals WHERE species_id = 5 ORDER BY name
+ </query>
+ <rule>
+ <where subject="?id" rel="greater" value="10"/>
+ <action>
+ <listitem uri="?" label="?name" />
+ </action>
+ </rule>
+ </template>
+</listbox>
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_storage_simple.xul b/dom/xul/templates/tests/chrome/test_tmpl_storage_simple.xul
new file mode 100644
index 000000000..0ac6d3857
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_storage_simple.xul
@@ -0,0 +1,52 @@
+<?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"?>
+
+<!--
+ storage simple
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <button anyid="true" label="Barn Owl / Tyto alba"/>
+ <button anyid="true" label="Emu / Dromaius novaehollandiae"/>
+ <button anyid="true" label="Raven / Corvus corax"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+copyToProfile('animals.sqlite');
+SimpleTest.waitForExplicitFinish();
+
+var testid ="storage simple";
+var queryType = "storage";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root"
+ datasources="profile:animals.sqlite" querytype="storage" ref=".">
+<template>
+ <query>SELECT * FROM animals WHERE species_id = 2 ORDER BY name</query>
+ <action>
+ <button uri="?" label="?name / ?specimen"/>
+ </action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_storage_sortintegerasc.xul b/dom/xul/templates/tests/chrome/test_tmpl_storage_sortintegerasc.xul
new file mode 100644
index 000000000..376cee25c
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_storage_sortintegerasc.xul
@@ -0,0 +1,75 @@
+<?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"?>
+
+<!--
+ storage sort integer asc
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <row anyid="true"> <label value="1"/> <label value="Tarantula"/> </row>
+ <row anyid="true"> <label value="2"/> <label value="Emu"/> </row>
+ <row anyid="true"> <label value="3"/> <label value="Barn Owl"/> </row>
+ <row anyid="true"> <label value="4"/> <label value="Raven"/> </row>
+ <row anyid="true"> <label value="5"/> <label value="Cod"/> </row>
+ <row anyid="true"> <label value="6"/> <label value="Swordfish"/> </row>
+ <row anyid="true"> <label value="7"/> <label value="Lion"/> </row>
+ <row anyid="true"> <label value="8"/> <label value="HIPPOPOTAMUS"/> </row>
+ <row anyid="true"> <label value="9"/> <label value="African Elephant"/> </row>
+ <row anyid="true"> <label value="10"/> <label value="LAMA"/> </row>
+ <row anyid="true"> <label value="11"/> <label value="Polar Bear"/> </row>
+ <row anyid="true"> <label value="12"/> <label value="aardvark"/> </row>
+ <row anyid="true"> <label value="13"/> <label value="Nine-banded Armadillo"/> </row>
+ <row anyid="true"> <label value="14"/> <label value="Gorilla"/> </row>
+ <row anyid="true"> <label value="15"/> <label value="Anaconda"/> </row>
+ <row anyid="true"> <label value="16"/> <label value="Chameleon"/> </row>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+copyToProfile('animals.sqlite');
+
+var testid ="storage sort integer asc";
+var queryType = "storage";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<grid xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" >
+ <columns>
+ <column flex="1"/>
+ <column flex="3"/>
+ </columns>
+ <rows id="root" datasources="profile:animals.sqlite" querytype="storage" ref="."
+ sort="?id" sortDirection="ascending">
+ <template>
+ <query>SELECT id, name FROM animals</query>
+ <action>
+ <row uri="?">
+ <label value="?id"/>
+ <label value="?name"/>
+ </row>
+ </action>
+ </template>
+ </rows>
+</grid>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_storage_sortintegerdesc.xul b/dom/xul/templates/tests/chrome/test_tmpl_storage_sortintegerdesc.xul
new file mode 100644
index 000000000..3ab75fcf4
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_storage_sortintegerdesc.xul
@@ -0,0 +1,78 @@
+<?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"?>
+
+<!--
+ storage sort integer desc
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <row anyid="true"> <label value="16"/> <label value="Chameleon"/> </row>
+ <row anyid="true"> <label value="15"/> <label value="Anaconda"/> </row>
+ <row anyid="true"> <label value="14"/> <label value="Gorilla"/> </row>
+ <row anyid="true"> <label value="13"/> <label value="Nine-banded Armadillo"/> </row>
+ <row anyid="true"> <label value="12"/> <label value="aardvark"/> </row>
+ <row anyid="true"> <label value="11"/> <label value="Polar Bear"/> </row>
+ <row anyid="true"> <label value="10"/> <label value="LAMA"/> </row>
+ <row anyid="true"> <label value="9"/> <label value="African Elephant"/> </row>
+ <row anyid="true"> <label value="8"/> <label value="HIPPOPOTAMUS"/> </row>
+ <row anyid="true"> <label value="7"/> <label value="Lion"/> </row>
+ <row anyid="true"> <label value="6"/> <label value="Swordfish"/> </row>
+ <row anyid="true"> <label value="5"/> <label value="Cod"/> </row>
+ <row anyid="true"> <label value="4"/> <label value="Raven"/> </row>
+ <row anyid="true"> <label value="3"/> <label value="Barn Owl"/> </row>
+ <row anyid="true"> <label value="2"/> <label value="Emu"/> </row>
+ <row anyid="true"> <label value="1"/> <label value="Tarantula"/> </row>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+copyToProfile('animals.sqlite');
+
+var testid ="storage sort integer desc";
+var queryType = "storage";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<grid xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" >
+ <columns>
+ <column flex="1"/>
+ <column flex="3"/>
+ </columns>
+ <rows id="root" datasources="profile:animals.sqlite" querytype="storage" ref="."
+ sort="?id" sortDirection="descending">
+ <template>
+ <query>SELECT id, name FROM animals</query>
+ <action>
+ <row uri="?">
+ <label value="?id"/>
+ <label value="?name"/>
+ </row>
+ </action>
+ </template>
+ </rows>
+</grid>
+
+
+
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_storage_sortstringasc.xul b/dom/xul/templates/tests/chrome/test_tmpl_storage_sortstringasc.xul
new file mode 100644
index 000000000..600db67c4
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_storage_sortstringasc.xul
@@ -0,0 +1,75 @@
+<?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"?>
+
+<!--
+ storage sort string asc
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <row anyid="true"> <label value="12"/> <label value="aardvark"/> </row>
+ <row anyid="true"> <label value="9"/> <label value="African Elephant"/> </row>
+ <row anyid="true"> <label value="15"/> <label value="Anaconda"/> </row>
+ <row anyid="true"> <label value="3"/> <label value="Barn Owl"/> </row>
+ <row anyid="true"> <label value="16"/> <label value="Chameleon"/> </row>
+ <row anyid="true"> <label value="5"/> <label value="Cod"/> </row>
+ <row anyid="true"> <label value="2"/> <label value="Emu"/> </row>
+ <row anyid="true"> <label value="14"/> <label value="Gorilla"/> </row>
+ <row anyid="true"> <label value="8"/> <label value="HIPPOPOTAMUS"/> </row>
+ <row anyid="true"> <label value="10"/> <label value="LAMA"/> </row>
+ <row anyid="true"> <label value="7"/> <label value="Lion"/> </row>
+ <row anyid="true"> <label value="13"/> <label value="Nine-banded Armadillo"/> </row>
+ <row anyid="true"> <label value="11"/> <label value="Polar Bear"/> </row>
+ <row anyid="true"> <label value="4"/> <label value="Raven"/> </row>
+ <row anyid="true"> <label value="6"/> <label value="Swordfish"/> </row>
+ <row anyid="true"> <label value="1"/> <label value="Tarantula"/> </row>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+copyToProfile('animals.sqlite');
+
+var testid ="storage sort string asc";
+var queryType = "storage";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<grid xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" >
+ <columns>
+ <column flex="1"/>
+ <column flex="3"/>
+ </columns>
+ <rows id="root" datasources="profile:animals.sqlite" querytype="storage" ref="."
+ sort="?name" sortDirection="ascending">
+ <template>
+ <query>SELECT id, name FROM animals</query>
+ <action>
+ <row uri="?">
+ <label value="?id"/>
+ <label value="?name"/>
+ </row>
+ </action>
+ </template>
+ </rows>
+</grid>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_storage_sortstringdesc.xul b/dom/xul/templates/tests/chrome/test_tmpl_storage_sortstringdesc.xul
new file mode 100644
index 000000000..b99c849e7
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_storage_sortstringdesc.xul
@@ -0,0 +1,75 @@
+<?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"?>
+
+<!--
+ storage sort string desc
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <row anyid="true"> <label value="1"/> <label value="Tarantula"/> </row>
+ <row anyid="true"> <label value="6"/> <label value="Swordfish"/> </row>
+ <row anyid="true"> <label value="4"/> <label value="Raven"/> </row>
+ <row anyid="true"> <label value="11"/> <label value="Polar Bear"/> </row>
+ <row anyid="true"> <label value="13"/> <label value="Nine-banded Armadillo"/> </row>
+ <row anyid="true"> <label value="7"/> <label value="Lion"/> </row>
+ <row anyid="true"> <label value="10"/> <label value="LAMA"/> </row>
+ <row anyid="true"> <label value="8"/> <label value="HIPPOPOTAMUS"/> </row>
+ <row anyid="true"> <label value="14"/> <label value="Gorilla"/> </row>
+ <row anyid="true"> <label value="2"/> <label value="Emu"/> </row>
+ <row anyid="true"> <label value="5"/> <label value="Cod"/> </row>
+ <row anyid="true"> <label value="16"/> <label value="Chameleon"/> </row>
+ <row anyid="true"> <label value="3"/> <label value="Barn Owl"/> </row>
+ <row anyid="true"> <label value="15"/> <label value="Anaconda"/> </row>
+ <row anyid="true"> <label value="9"/> <label value="African Elephant"/> </row>
+ <row anyid="true"> <label value="12"/> <label value="aardvark"/> </row>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+copyToProfile('animals.sqlite');
+
+var testid ="storage sort string desc";
+var queryType = "storage";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<grid xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" >
+ <columns>
+ <column flex="1"/>
+ <column flex="3"/>
+ </columns>
+ <rows id="root" datasources="profile:animals.sqlite" querytype="storage" ref="."
+ sort="?name" sortDirection="descending">
+ <template>
+ <query>SELECT id, name FROM animals</query>
+ <action>
+ <row uri="?">
+ <label value="?id"/>
+ <label value="?name"/>
+ </row>
+ </action>
+ </template>
+ </rows>
+</grid>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_storage_tree.xul b/dom/xul/templates/tests/chrome/test_tmpl_storage_tree.xul
new file mode 100644
index 000000000..9ca042713
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_storage_tree.xul
@@ -0,0 +1,122 @@
+<?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"?>
+
+<!--
+ storage tree
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols orient="horizontal">
+ <treecol id="species" primary="true" label="Species" flex="2" ordinal="1"/>
+ <treecol id="name" label="Common name" flex="2" ordinal="3"/>
+ <treecol id="specimen" label="Specimen" flex="3" ordinal="5"/>
+ <treecol id="id" label="id" flex="1" ordinal="7"/>
+ </treecols>
+ <treechildren>
+ <treeitem anyid="true"> <treerow>
+ <treecell label="arachnids" /> <treecell label="Tarantula"/> <treecell label="Avicularia avicularia" /> <treecell label="1"/>
+ </treerow> </treeitem>
+ <treeitem anyid="true"> <treerow>
+ <treecell label="birds" /> <treecell label="Barn Owl"/> <treecell label="Tyto alba" /> <treecell label="3"/>
+ </treerow> </treeitem>
+ <treeitem anyid="true"> <treerow>
+ <treecell label="birds" /> <treecell label="Emu"/> <treecell label="Dromaius novaehollandiae" /> <treecell label="2"/>
+ </treerow> </treeitem>
+ <treeitem anyid="true"> <treerow>
+ <treecell label="birds" /> <treecell label="Raven"/> <treecell label="Corvus corax" /> <treecell label="4"/>
+ </treerow> </treeitem>
+ <treeitem anyid="true"> <treerow>
+ <treecell label="fish" /> <treecell label="Cod"/> <treecell label="Gadus morhua" /> <treecell label="5"/>
+ </treerow> </treeitem>
+ <treeitem anyid="true"> <treerow>
+ <treecell label="fish" /> <treecell label="Swordfish"/> <treecell label="Xiphias gladius" /> <treecell label="6"/>
+ </treerow> </treeitem>
+ <treeitem anyid="true"> <treerow>
+ <treecell label="mammals" /> <treecell label="African Elephant"/> <treecell label="Loxodonta africana" /> <treecell label="9"/>
+ </treerow> </treeitem>
+ <treeitem anyid="true"> <treerow>
+ <treecell label="mammals" /> <treecell label="Gorilla"/> <treecell label="Gorilla gorilla" /> <treecell label="14"/>
+ </treerow> </treeitem>
+ <treeitem anyid="true"> <treerow>
+ <treecell label="mammals" /> <treecell label="HIPPOPOTAMUS"/> <treecell label="Hippopotamus amphibius" /> <treecell label="8"/>
+ </treerow> </treeitem>
+ <treeitem anyid="true"> <treerow>
+ <treecell label="mammals" /> <treecell label="LAMA"/> <treecell label="Lama glama" /> <treecell label="10"/>
+ </treerow> </treeitem>
+ <treeitem anyid="true"> <treerow>
+ <treecell label="mammals" /> <treecell label="Lion"/> <treecell label="Panthera leo" /> <treecell label="7"/>
+ </treerow> </treeitem>
+ <treeitem anyid="true"> <treerow>
+ <treecell label="mammals" /> <treecell label="Nine-banded Armadillo"/> <treecell label="Dasypus novemcinctus" /> <treecell label="13"/>
+ </treerow> </treeitem>
+ <treeitem anyid="true"> <treerow>
+ <treecell label="mammals" /> <treecell label="Polar Bear"/> <treecell label="Thalarctos maritimus" /> <treecell label="11"/>
+ </treerow> </treeitem>
+ <treeitem anyid="true"> <treerow>
+ <treecell label="mammals" /> <treecell label="aardvark"/> <treecell label="Orycteropus afer" /> <treecell label="12"/>
+ </treerow> </treeitem>
+ <treeitem anyid="true"> <treerow>
+ <treecell label="reptiles" /> <treecell label="Anaconda"/> <treecell label="Eunectes murinus" /> <treecell label="15"/>
+ </treerow> </treeitem>
+ <treeitem anyid="true"> <treerow>
+ <treecell label="reptiles" /> <treecell label="Chameleon"/> <treecell label="Chamaeleo chamaelon" /> <treecell label="16"/>
+ </treerow> </treeitem>
+ </treechildren>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+copyToProfile('animals.sqlite');
+
+var testid ="storage tree";
+var queryType = "storage";
+var isTreeBuilder = true;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<tree xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root"
+ flex="1" datasources="profile:animals.sqlite" ref="*" querytype="storage" flags="dont-build-content">
+ <treecols>
+ <treecol id="species" primary="true" label="Species" flex="2"/>
+ <treecol id="name" label="Common name" flex="2"/>
+ <treecol id="specimen" label="Specimen" flex="3"/>
+ <treecol id="id" label="id" flex="1"/>
+ </treecols>
+ <template>
+ <query>
+ SELECT a.id, a.name, a.specimen, s.name as species FROM animals a, species s
+ WHERE a.species_id = s.id ORDER BY species, a.name</query>
+ <action>
+ <treechildren>
+ <treeitem uri="?">
+ <treerow>
+ <treecell label="?species"/>
+ <treecell label="?name"/>
+ <treecell label="?specimen"/>
+ <treecell label="?id"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </action>
+ </template>
+ </tree>
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntax.xul b/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntax.xul
new file mode 100644
index 000000000..bfb494c79
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntax.xul
@@ -0,0 +1,158 @@
+<?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"?>
+
+<!--
+ tree element - query syntax
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols id="treecols" orient="horizontal">
+ <treecol id="treecol" primary="true" label="Name" ordinal="1"/>
+ <treecol label="Species" ordinal="3"/>
+ </treecols>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/emu">
+ <treerow>
+ <treecell label="Emu"/>
+ <treecell step="-6" label="12"/>
+ <treecell step="6" label="0"/>
+ </treerow>
+ </treeitem>
+ <treeitem step="1" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx">
+ <treerow>
+ <treecell label="Archaeopteryx"/>
+ <treecell step="-4"/>
+ <treecell step="4" label="5"/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-2" id="http://www.some-fictitious-zoo.com/birds/barnowl">
+ <treerow>
+ <treecell label="Barn Owl"/>
+ <treecell label="4"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/raven">
+ <treerow>
+ <treecell step="-7" label="Raven"/>
+ <treecell step="7" label="Crow"/>
+ <treecell step="-5" label="0"/>
+ <treecell step="5" label="2"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="tree element - query syntax";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ 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, '2', true);
+ },
+ // step 2
+ 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);
+ },
+ // step 3
+ function(targetds, root) {
+ targetds.Assert(RDF.GetResource(ZOO_NS + 'birds/archaeopteryx'),
+ RDF.GetResource(ZOO_NS + 'rdf#species'),
+ RDF.GetLiteral('Archaeopteryx lithographica'), true);
+ },
+ // step 4
+ function(targetds, root) {
+ targetds.Assert(RDF.GetResource(ZOO_NS + 'birds/archaeopteryx'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ RDF.GetLiteral('5'), true);
+ },
+ // step 5
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'birds/raven');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#specimens');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'birds/raven'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ oldval, RDF.GetLiteral('2'));
+ },
+ // step 6
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'birds/emu');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#specimens');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'birds/emu'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ oldval, RDF.GetLiteral('0'));
+ },
+ // step 7
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'birds/raven');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#name');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'birds/raven'),
+ RDF.GetResource(ZOO_NS + 'rdf#name'),
+ oldval, RDF.GetLiteral('Crow'));
+ }
+];
+]]>
+</script>
+
+<tree xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" hidevscroll="true" hidehscroll="true" datasources="rdf:null" flags="" id="root" ref="http://www.some-fictitious-zoo.com/birds">
+<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"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<rule>
+<bindings>
+<binding subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#specimens" object="?specimens"/>
+</bindings>
+<action>
+<treechildren>
+<treeitem uri="?child">
+<treerow>
+<treecell label="?name"/>
+<treecell label="?specimens"/>
+</treerow>
+</treeitem>
+</treechildren>
+</action>
+</rule>
+</template>
+</tree>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxnotrecursive.xul b/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxnotrecursive.xul
new file mode 100644
index 000000000..9a07372fe
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxnotrecursive.xul
@@ -0,0 +1,145 @@
+<?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"?>
+
+<!--
+ tree element - query syntax not recursive
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols id="treecols" orient="horizontal">
+ <treecol id="treecol" primary="true" label="Name" ordinal="1"/>
+ <treecol label="Species" ordinal="3"/>
+ </treecols>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/humans/sarah">
+ <treerow>
+ <treecell label="Sarah"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false">
+ <treerow>
+ <treecell label="Mammals"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false" open="true">
+ <treerow>
+ <treecell label="Mammals"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-4" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true">
+ <treerow>
+ <treecell label="Crustaceans"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="4" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true">
+ <treerow>
+ <treecell label="Crustaceans"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/emu">
+ <treerow>
+ <treecell label="Emu"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="tree element - query syntax not recursive";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = true;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ if (root.view && 1 < root.view.rowCount && root.view.isContainer(1))
+ root.view.toggleOpenState(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);
+ },
+ // 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);
+ },
+ // step 5
+ function(targetds, root) {
+ if (root.view && 11 < root.view.rowCount && root.view.isContainer(11))
+ root.view.toggleOpenState(11);
+ },
+ // step 6
+ 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="rdf:null" ref="http://www.some-fictitious-zoo.com/marked" flags="dont-recurse" 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"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<rule>
+<action>
+<treechildren>
+<treeitem uri="?child">
+<treerow>
+<treecell label="?name"/>
+<treecell/>
+</treerow>
+</treeitem>
+</treechildren>
+</action>
+</rule>
+</template>
+</tree>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxnotrecursivetreebuilder.xul b/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxnotrecursivetreebuilder.xul
new file mode 100644
index 000000000..106d4d7cd
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxnotrecursivetreebuilder.xul
@@ -0,0 +1,145 @@
+<?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"?>
+
+<!--
+ tree element - query syntax not recursive tree builder
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols id="treecols" orient="horizontal">
+ <treecol id="treecol" primary="true" label="Name" ordinal="1"/>
+ <treecol label="Species" ordinal="3"/>
+ </treecols>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/humans/sarah">
+ <treerow>
+ <treecell label="Sarah"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="true">
+ <treerow>
+ <treecell label="Mammals"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="true" open="">
+ <treerow>
+ <treecell label="Mammals"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-4" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true">
+ <treerow>
+ <treecell label="Crustaceans"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="4" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true">
+ <treerow>
+ <treecell label="Crustaceans"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/emu">
+ <treerow>
+ <treecell label="Emu"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="tree element - query syntax not recursive tree builder";
+var queryType = "rdf";
+var isTreeBuilder = true;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ if (root.view && 1 < root.view.rowCount && root.view.isContainer(1))
+ root.view.toggleOpenState(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);
+ },
+ // 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);
+ },
+ // step 5
+ function(targetds, root) {
+ if (root.view && 11 < root.view.rowCount && root.view.isContainer(11))
+ root.view.toggleOpenState(11);
+ },
+ // step 6
+ 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="rdf:null" ref="http://www.some-fictitious-zoo.com/marked" flags="dont-recurse dont-build-content" 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"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<rule>
+<action>
+<treechildren>
+<treeitem uri="?child">
+<treerow>
+<treecell label="?name"/>
+<treecell/>
+</treerow>
+</treeitem>
+</treechildren>
+</action>
+</rule>
+</template>
+</tree>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursive.xul b/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursive.xul
new file mode 100644
index 000000000..1002cd00c
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursive.xul
@@ -0,0 +1,215 @@
+<?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"?>
+
+<!--
+ tree element - query syntax recursive
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols id="treecols" orient="horizontal">
+ <treecol id="treecol" primary="true" label="Name" ordinal="1"/>
+ <treecol label="Species" ordinal="3"/>
+ </treecols>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/humans/sarah">
+ <treerow>
+ <treecell label="Sarah"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false">
+ <treerow>
+ <treecell label="Mammals"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false" open="true">
+ <treerow>
+ <treecell label="Mammals"/>
+ <treecell/>
+ </treerow>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/lion">
+ <treerow>
+ <treecell label="Lion"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/hippopotamus">
+ <treerow>
+ <treecell label="HIPPOPOTAMUS"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/africanelephant">
+ <treerow>
+ <treecell label="African Elephant"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="2" id="http://www.some-fictitious-zoo.com/mammals/koala">
+ <treerow>
+ <treecell label="Koala"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/llama">
+ <treerow>
+ <treecell label="LLAMA"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/polarbear">
+ <treerow>
+ <treecell label="Polar Bear"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/aardvark">
+ <treerow>
+ <treecell label="aardvark"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo">
+ <treerow>
+ <treecell label="Nine-banded Armadillo"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/gorilla">
+ <treerow>
+ <treecell label="Gorilla"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ <treeitem step="-3" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true">
+ <treerow>
+ <treecell label="Crustaceans"/>
+ <treecell/>
+ </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">
+ <treerow>
+ <treecell label="Crustaceans"/>
+ <treecell/>
+ </treerow>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/crustaceans/lobster">
+ <treerow>
+ <treecell label="Lobster"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/emu">
+ <treerow>
+ <treecell label="Emu"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="tree element - query syntax recursive";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = true;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ if (root.view && 1 < root.view.rowCount && root.view.isContainer(1))
+ root.view.toggleOpenState(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);
+ },
+ // 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);
+ },
+ // step 5
+ function(targetds, root) {
+ if (root.view && 11 < root.view.rowCount && root.view.isContainer(11))
+ root.view.toggleOpenState(11);
+ },
+ // step 6
+ 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="rdf:null" 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"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<rule>
+<action>
+<treechildren>
+<treeitem uri="?child">
+<treerow>
+<treecell label="?name"/>
+<treecell/>
+</treerow>
+</treeitem>
+</treechildren>
+</action>
+</rule>
+</template>
+</tree>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursivemultiplerules.xul b/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursivemultiplerules.xul
new file mode 100644
index 000000000..282beb69d
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursivemultiplerules.xul
@@ -0,0 +1,268 @@
+<?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"?>
+
+<!--
+ tree element - query syntax recursive multiple rules
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols id="treecols" orient="horizontal">
+ <treecol id="treecol" primary="true" label="Name" ordinal="1"/>
+ <treecol label="Species" ordinal="3"/>
+ </treecols>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/humans/sarah">
+ <treerow>
+ <treecell label="Sarah"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false">
+ <treerow>
+ <treecell/>
+ <treecell label="Is this cool: Mammals?"/>
+ </treerow>
+ </treeitem>
+ <treeitem step="1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false" open="true">
+ <treerow>
+ <treecell/>
+ <treecell label="Is this cool: Mammals?"/>
+ </treerow>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/lion">
+ <treerow>
+ <treecell/>
+ <treecell label="Is this cool: Lion?"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/hippopotamus">
+ <treerow>
+ <treecell label="HIPPOPOTAMUS"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/africanelephant">
+ <treerow>
+ <treecell/>
+ <treecell label="Is this cool: African Elephant?"/>
+ </treerow>
+ </treeitem>
+ <treeitem step="2" id="http://www.some-fictitious-zoo.com/mammals/koala">
+ <treerow>
+ <treecell/>
+ <treecell label="Is this cool: Koala?"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/llama">
+ <treerow>
+ <treecell/>
+ <treecell label="Is this cool: LLAMA?"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/polarbear">
+ <treerow>
+ <treecell/>
+ <treecell label="Is this cool: Polar Bear?"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/aardvark">
+ <treerow>
+ <treecell label="aardvark"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo">
+ <treerow>
+ <treecell/>
+ <treecell label="Is this cool: Nine-banded Armadillo?"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/gorilla">
+ <treerow>
+ <treecell/>
+ <treecell label="Is this cool: Gorilla?"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ <treeitem step="-3" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true">
+ <treerow>
+ <treecell label="Crustaceans"/>
+ <treecell/>
+ </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="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>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="tree element - query syntax recursive multiple rules";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = true;
+var expectedOutput = document.getElementById("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) {
+ 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"
+ flags="logging" datasources="rdf:null" 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"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<rule>
+<conditions>
+<where subject="?name" rel="contains" value="l" ignorecase="true"/>
+</conditions>
+<action>
+<treechildren>
+<treeitem uri="?child">
+<treerow>
+<treecell/>
+<treecell label="Is this cool: ?name^??"/>
+</treerow>
+</treeitem>
+</treechildren>
+</action>
+</rule>
+<rule>
+<action>
+<treechildren>
+<treeitem uri="?child">
+<treerow>
+<treecell label="?name"/>
+<treecell/>
+</treerow>
+</treeitem>
+</treechildren>
+</action>
+</rule>
+</template>
+</tree>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursivemultiplerulestreebuilder.xul b/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursivemultiplerulestreebuilder.xul
new file mode 100644
index 000000000..8c9e5c012
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursivemultiplerulestreebuilder.xul
@@ -0,0 +1,230 @@
+<?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"?>
+
+<!--
+ tree element - query syntax recursive multiple rules tree builder
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols id="treecols" orient="horizontal">
+ <treecol id="treecol" primary="true" label="Name" ordinal="1"/>
+ <treecol label="Species" ordinal="3"/>
+ </treecols>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/humans/sarah">
+ <treerow>
+ <treecell label="Sarah"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false">
+ <treerow>
+ <treecell/>
+ <treecell label="Is this cool: Mammals?"/>
+ </treerow>
+ </treeitem>
+ <treeitem step="1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false" open="true">
+ <treerow>
+ <treecell/>
+ <treecell label="Is this cool: Mammals?"/>
+ </treerow>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/lion">
+ <treerow>
+ <treecell/>
+ <treecell label="Is this cool: Lion?"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/hippopotamus">
+ <treerow>
+ <treecell label="HIPPOPOTAMUS"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/africanelephant">
+ <treerow>
+ <treecell/>
+ <treecell label="Is this cool: African Elephant?"/>
+ </treerow>
+ </treeitem>
+ <treeitem step="2" id="http://www.some-fictitious-zoo.com/mammals/koala">
+ <treerow>
+ <treecell/>
+ <treecell label="Is this cool: Koala?"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/llama">
+ <treerow>
+ <treecell/>
+ <treecell label="Is this cool: LLAMA?"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/polarbear">
+ <treerow>
+ <treecell/>
+ <treecell label="Is this cool: Polar Bear?"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/aardvark">
+ <treerow>
+ <treecell label="aardvark"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo">
+ <treerow>
+ <treecell/>
+ <treecell label="Is this cool: Nine-banded Armadillo?"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/gorilla">
+ <treerow>
+ <treecell/>
+ <treecell label="Is this cool: Gorilla?"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ <treeitem step="-3" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true">
+ <treerow>
+ <treecell label="Crustaceans"/>
+ <treecell/>
+ </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">
+ <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>
+ </treechildren>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/emu">
+ <treerow>
+ <treecell label="Emu"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="tree element - query syntax recursive multiple rules tree builder";
+var queryType = "rdf";
+var isTreeBuilder = true;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = true;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ if (root.view && 1 < root.view.rowCount && root.view.isContainer(1))
+ root.view.toggleOpenState(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);
+ },
+ // 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);
+ },
+ // step 5
+ function(targetds, root) {
+ if (root.view && 11 < root.view.rowCount && root.view.isContainer(11))
+ root.view.toggleOpenState(11);
+ },
+ // step 6
+ 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="rdf:null" ref="http://www.some-fictitious-zoo.com/marked" flags=" dont-build-content" 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"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<rule>
+<conditions>
+<where subject="?name" rel="contains" value="l" ignorecase="true"/>
+</conditions>
+<action>
+<treechildren>
+<treeitem uri="?child">
+<treerow>
+<treecell/>
+<treecell label="Is this cool: ?name^??"/>
+</treerow>
+</treeitem>
+</treechildren>
+</action>
+</rule>
+<rule>
+<action>
+<treechildren>
+<treeitem uri="?child">
+<treerow>
+<treecell label="?name"/>
+<treecell/>
+</treerow>
+</treeitem>
+</treechildren>
+</action>
+</rule>
+</template>
+</tree>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursivetreebuilder.xul b/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursivetreebuilder.xul
new file mode 100644
index 000000000..387279240
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxrecursivetreebuilder.xul
@@ -0,0 +1,215 @@
+<?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"?>
+
+<!--
+ tree element - query syntax recursive tree builder
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols id="treecols" orient="horizontal">
+ <treecol id="treecol" primary="true" label="Name" ordinal="1"/>
+ <treecol label="Species" ordinal="3"/>
+ </treecols>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/humans/sarah">
+ <treerow>
+ <treecell label="Sarah"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false">
+ <treerow>
+ <treecell label="Mammals"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false" open="true">
+ <treerow>
+ <treecell label="Mammals"/>
+ <treecell/>
+ </treerow>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/lion">
+ <treerow>
+ <treecell label="Lion"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/hippopotamus">
+ <treerow>
+ <treecell label="HIPPOPOTAMUS"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/africanelephant">
+ <treerow>
+ <treecell label="African Elephant"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="2" id="http://www.some-fictitious-zoo.com/mammals/koala">
+ <treerow>
+ <treecell label="Koala"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/llama">
+ <treerow>
+ <treecell label="LLAMA"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/polarbear">
+ <treerow>
+ <treecell label="Polar Bear"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/aardvark">
+ <treerow>
+ <treecell label="aardvark"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo">
+ <treerow>
+ <treecell label="Nine-banded Armadillo"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/gorilla">
+ <treerow>
+ <treecell label="Gorilla"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ <treeitem step="-3" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true">
+ <treerow>
+ <treecell label="Crustaceans"/>
+ <treecell/>
+ </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">
+ <treerow>
+ <treecell label="Crustaceans"/>
+ <treecell/>
+ </treerow>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/crustaceans/lobster">
+ <treerow>
+ <treecell label="Lobster"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/emu">
+ <treerow>
+ <treecell label="Emu"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="tree element - query syntax recursive tree builder";
+var queryType = "rdf";
+var isTreeBuilder = true;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = true;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ if (root.view && 1 < root.view.rowCount && root.view.isContainer(1))
+ root.view.toggleOpenState(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);
+ },
+ // 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);
+ },
+ // step 5
+ function(targetds, root) {
+ if (root.view && 11 < root.view.rowCount && root.view.isContainer(11))
+ root.view.toggleOpenState(11);
+ },
+ // step 6
+ 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="rdf:null" ref="http://www.some-fictitious-zoo.com/marked" flags=" dont-build-content" 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"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<rule>
+<action>
+<treechildren>
+<treeitem uri="?child">
+<treerow>
+<treecell label="?name"/>
+<treecell/>
+</treerow>
+</treeitem>
+</treechildren>
+</action>
+</rule>
+</template>
+</tree>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxtreebuilder.xul b/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxtreebuilder.xul
new file mode 100644
index 000000000..e857c8e0e
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_treeelementquerysyntaxtreebuilder.xul
@@ -0,0 +1,158 @@
+<?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"?>
+
+<!--
+ tree element - query syntax tree builder
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols id="treecols" orient="horizontal">
+ <treecol id="treecol" primary="true" label="Name" ordinal="1"/>
+ <treecol label="Species" ordinal="3"/>
+ </treecols>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/emu">
+ <treerow>
+ <treecell label="Emu"/>
+ <treecell step="-6" label="12"/>
+ <treecell step="6" label="0"/>
+ </treerow>
+ </treeitem>
+ <treeitem step="1" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx">
+ <treerow>
+ <treecell label="Archaeopteryx"/>
+ <treecell step="-4"/>
+ <treecell step="4" label="5"/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-2" id="http://www.some-fictitious-zoo.com/birds/barnowl">
+ <treerow>
+ <treecell label="Barn Owl"/>
+ <treecell label="4"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/raven">
+ <treerow>
+ <treecell step="-7" label="Raven"/>
+ <treecell step="7" label="Crow"/>
+ <treecell step="-5" label="0"/>
+ <treecell step="5" label="2"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="tree element - query syntax tree builder";
+var queryType = "rdf";
+var isTreeBuilder = true;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = true;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ 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, '2', true);
+ },
+ // step 2
+ 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);
+ },
+ // step 3
+ function(targetds, root) {
+ targetds.Assert(RDF.GetResource(ZOO_NS + 'birds/archaeopteryx'),
+ RDF.GetResource(ZOO_NS + 'rdf#species'),
+ RDF.GetLiteral('Archaeopteryx lithographica'), true);
+ },
+ // step 4
+ function(targetds, root) {
+ targetds.Assert(RDF.GetResource(ZOO_NS + 'birds/archaeopteryx'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ RDF.GetLiteral('5'), true);
+ },
+ // step 5
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'birds/raven');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#specimens');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'birds/raven'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ oldval, RDF.GetLiteral('2'));
+ },
+ // step 6
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'birds/emu');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#specimens');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'birds/emu'),
+ RDF.GetResource(ZOO_NS + 'rdf#specimens'),
+ oldval, RDF.GetLiteral('0'));
+ },
+ // step 7
+ function(targetds, root) {
+ var subject = RDF.GetResource(ZOO_NS + 'birds/raven');
+ var predicate = RDF.GetResource(ZOO_NS + 'rdf#name');
+ var oldval = targetds.GetTarget(subject, predicate, true);
+ targetds.Change(RDF.GetResource(ZOO_NS + 'birds/raven'),
+ RDF.GetResource(ZOO_NS + 'rdf#name'),
+ oldval, RDF.GetLiteral('Crow'));
+ }
+];
+]]>
+</script>
+
+<tree xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" hidevscroll="true" hidehscroll="true" datasources="rdf:null" flags="dont-build-content" id="root" ref="http://www.some-fictitious-zoo.com/birds">
+<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"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<rule>
+<bindings>
+<binding subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#specimens" object="?specimens"/>
+</bindings>
+<action>
+<treechildren>
+<treeitem uri="?child">
+<treerow>
+<treecell label="?name"/>
+<treecell label="?specimens"/>
+</treerow>
+</treeitem>
+</treechildren>
+</action>
+</rule>
+</template>
+</tree>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_treeelementsimplesyntaxnotrecursive.xul b/dom/xul/templates/tests/chrome/test_tmpl_treeelementsimplesyntaxnotrecursive.xul
new file mode 100644
index 000000000..ff447916e
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_treeelementsimplesyntaxnotrecursive.xul
@@ -0,0 +1,136 @@
+<?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"?>
+
+<!--
+ tree element - simple syntax not recursive
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols id="treecols" orient="horizontal">
+ <treecol id="treecol" primary="true" label="Name" ordinal="1"/>
+ <treecol label="Species" ordinal="3"/>
+ </treecols>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/humans/sarah">
+ <treerow>
+ <treecell label="Sarah"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false">
+ <treerow>
+ <treecell label="Mammals"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false" open="true">
+ <treerow>
+ <treecell label="Mammals"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-4" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true">
+ <treerow>
+ <treecell label="Crustaceans"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="4" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true">
+ <treerow>
+ <treecell label="Crustaceans"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/emu">
+ <treerow>
+ <treecell label="Emu"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="tree element - simple syntax not recursive";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = true;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ if (root.view && 1 < root.view.rowCount && root.view.isContainer(1))
+ root.view.toggleOpenState(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);
+ },
+ // 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);
+ },
+ // step 5
+ function(targetds, root) {
+ if (root.view && 11 < root.view.rowCount && root.view.isContainer(11))
+ root.view.toggleOpenState(11);
+ },
+ // step 6
+ 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="rdf:null" flags="dont-recurse" 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">
+<treechildren>
+<treeitem uri="rdf:*">
+<treerow>
+<treecell label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+<treecell/>
+</treerow>
+</treeitem>
+</treechildren>
+</template>
+</tree>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_treeelementsimplesyntaxnotrecursivetreebuilder.xul b/dom/xul/templates/tests/chrome/test_tmpl_treeelementsimplesyntaxnotrecursivetreebuilder.xul
new file mode 100644
index 000000000..793506143
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_treeelementsimplesyntaxnotrecursivetreebuilder.xul
@@ -0,0 +1,136 @@
+<?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"?>
+
+<!--
+ tree element - simple syntax not recursive tree builder
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols id="treecols" orient="horizontal">
+ <treecol id="treecol" primary="true" label="Name" ordinal="1"/>
+ <treecol label="Species" ordinal="3"/>
+ </treecols>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/humans/sarah">
+ <treerow>
+ <treecell label="Sarah"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="true">
+ <treerow>
+ <treecell label="Mammals"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="true" open="">
+ <treerow>
+ <treecell label="Mammals"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-4" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true">
+ <treerow>
+ <treecell label="Crustaceans"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="4" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true">
+ <treerow>
+ <treecell label="Crustaceans"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/emu">
+ <treerow>
+ <treecell label="Emu"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="tree element - simple syntax not recursive tree builder";
+var queryType = "rdf";
+var isTreeBuilder = true;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ if (root.view && 1 < root.view.rowCount && root.view.isContainer(1))
+ root.view.toggleOpenState(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);
+ },
+ // 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);
+ },
+ // step 5
+ function(targetds, root) {
+ if (root.view && 11 < root.view.rowCount && root.view.isContainer(11))
+ root.view.toggleOpenState(11);
+ },
+ // step 6
+ 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="rdf:null" flags="dont-recurse dont-build-content" 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">
+<treechildren>
+<treeitem uri="rdf:*">
+<treerow>
+<treecell label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+<treecell/>
+</treerow>
+</treeitem>
+</treechildren>
+</template>
+</tree>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_treeelementsimplesyntaxrecursive.xul b/dom/xul/templates/tests/chrome/test_tmpl_treeelementsimplesyntaxrecursive.xul
new file mode 100644
index 000000000..009dd1367
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_treeelementsimplesyntaxrecursive.xul
@@ -0,0 +1,206 @@
+<?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"?>
+
+<!--
+ tree element - simple syntax recursive
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols id="treecols" orient="horizontal">
+ <treecol id="treecol" primary="true" label="Name" ordinal="1"/>
+ <treecol label="Species" ordinal="3"/>
+ </treecols>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/humans/sarah">
+ <treerow>
+ <treecell label="Sarah"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false">
+ <treerow>
+ <treecell label="Mammals"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false" open="true">
+ <treerow>
+ <treecell label="Mammals"/>
+ <treecell/>
+ </treerow>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/lion">
+ <treerow>
+ <treecell label="Lion"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/hippopotamus">
+ <treerow>
+ <treecell label="HIPPOPOTAMUS"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/africanelephant">
+ <treerow>
+ <treecell label="African Elephant"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="2" id="http://www.some-fictitious-zoo.com/mammals/koala">
+ <treerow>
+ <treecell label="Koala"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/llama">
+ <treerow>
+ <treecell label="LLAMA"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/polarbear">
+ <treerow>
+ <treecell label="Polar Bear"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/aardvark">
+ <treerow>
+ <treecell label="aardvark"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo">
+ <treerow>
+ <treecell label="Nine-banded Armadillo"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/gorilla">
+ <treerow>
+ <treecell label="Gorilla"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ <treeitem step="-3" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true">
+ <treerow>
+ <treecell label="Crustaceans"/>
+ <treecell/>
+ </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">
+ <treerow>
+ <treecell label="Crustaceans"/>
+ <treecell/>
+ </treerow>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/crustaceans/lobster">
+ <treerow>
+ <treecell label="Lobster"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/emu">
+ <treerow>
+ <treecell label="Emu"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="tree element - simple syntax recursive";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = true;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ if (root.view && 1 < root.view.rowCount && root.view.isContainer(1))
+ root.view.toggleOpenState(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);
+ },
+ // 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);
+ },
+ // step 5
+ function(targetds, root) {
+ if (root.view && 11 < root.view.rowCount && root.view.isContainer(11))
+ root.view.toggleOpenState(11);
+ },
+ // step 6
+ 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="rdf:null" flags="" 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">
+<treechildren>
+<treeitem uri="rdf:*">
+<treerow>
+<treecell label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+<treecell/>
+</treerow>
+</treeitem>
+</treechildren>
+</template>
+</tree>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_treeelementsimplesyntaxrecursivetreebuilder.xul b/dom/xul/templates/tests/chrome/test_tmpl_treeelementsimplesyntaxrecursivetreebuilder.xul
new file mode 100644
index 000000000..3cd7a8f07
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_treeelementsimplesyntaxrecursivetreebuilder.xul
@@ -0,0 +1,206 @@
+<?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"?>
+
+<!--
+ tree element - simple syntax recursive tree builder
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols id="treecols" orient="horizontal">
+ <treecol id="treecol" primary="true" label="Name" ordinal="1"/>
+ <treecol label="Species" ordinal="3"/>
+ </treecols>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/humans/sarah">
+ <treerow>
+ <treecell label="Sarah"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false">
+ <treerow>
+ <treecell label="Mammals"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="1" id="http://www.some-fictitious-zoo.com/mammals" container="true" empty="false" open="true">
+ <treerow>
+ <treecell label="Mammals"/>
+ <treecell/>
+ </treerow>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/lion">
+ <treerow>
+ <treecell label="Lion"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/hippopotamus">
+ <treerow>
+ <treecell label="HIPPOPOTAMUS"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/africanelephant">
+ <treerow>
+ <treecell label="African Elephant"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="2" id="http://www.some-fictitious-zoo.com/mammals/koala">
+ <treerow>
+ <treecell label="Koala"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/llama">
+ <treerow>
+ <treecell label="LLAMA"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/polarbear">
+ <treerow>
+ <treecell label="Polar Bear"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/aardvark">
+ <treerow>
+ <treecell label="aardvark"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo">
+ <treerow>
+ <treecell label="Nine-banded Armadillo"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/mammals/gorilla">
+ <treerow>
+ <treecell label="Gorilla"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ <treeitem step="-3" id="http://www.some-fictitious-zoo.com/crustaceans" container="true" empty="true">
+ <treerow>
+ <treecell label="Crustaceans"/>
+ <treecell/>
+ </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">
+ <treerow>
+ <treecell label="Crustaceans"/>
+ <treecell/>
+ </treerow>
+ <treechildren>
+ <treeitem id="http://www.some-fictitious-zoo.com/crustaceans/lobster">
+ <treerow>
+ <treecell label="Lobster"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/emu">
+ <treerow>
+ <treecell label="Emu"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="tree element - simple syntax recursive tree builder";
+var queryType = "rdf";
+var isTreeBuilder = true;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = true;
+var expectedOutput = document.getElementById("output");
+
+var changes = [
+ // step 1
+ function(targetds, root) {
+ if (root.view && 1 < root.view.rowCount && root.view.isContainer(1))
+ root.view.toggleOpenState(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);
+ },
+ // 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);
+ },
+ // step 5
+ function(targetds, root) {
+ if (root.view && 11 < root.view.rowCount && root.view.isContainer(11))
+ root.view.toggleOpenState(11);
+ },
+ // step 6
+ 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="rdf:null" flags="dont-build-content" 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">
+<treechildren>
+<treeitem uri="rdf:*">
+<treerow>
+<treecell label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+<treecell/>
+</treerow>
+</treeitem>
+</treechildren>
+</template>
+</tree>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_treeelementtreecell.xul b/dom/xul/templates/tests/chrome/test_tmpl_treeelementtreecell.xul
new file mode 100644
index 000000000..43c926ec8
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_treeelementtreecell.xul
@@ -0,0 +1,133 @@
+<?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"?>
+
+<!--
+ tree element - treecell
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols id="treecols" orient="horizontal">
+ <treecol id="treecol" primary="true" label="Name" ordinal="1"/>
+ <treecol label="Species" ordinal="3"/>
+ </treecols>
+ <treechildren>
+ <treeitem step="3" id="http://www.some-fictitious-zoo.com/birds/wren">
+ <treerow>
+ <treecell label="Wren"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/emu">
+ <treerow>
+ <treecell label="Emu"/>
+ <treecell label="Dromaius novaehollandiae"/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl">
+ <treerow>
+ <treecell label="Barn Owl"/>
+ <treecell label="Tyto alba"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/raven">
+ <treerow>
+ <treecell label="Raven"/>
+ <treecell label="Corvus corax"/>
+ </treerow>
+ </treeitem>
+ <treeitem step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx">
+ <treerow>
+ <treecell label="Archaeopteryx"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin">
+ <treerow>
+ <treecell label="Emperor Penguin"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="tree element - treecell";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<tree xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds" hidevscroll="true" hidehscroll="true">
+<treecols orient="horizontal" id="treecols">
+<treecol id="treecol" primary="true" label="Name" ordinal="1"/>
+<treecol label="Species" ordinal="3"/>
+</treecols>
+<template id="template">
+<treechildren id="treechildren">
+<treeitem uri="rdf:*">
+<treerow>
+<treecell label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+<treecell label="rdf:http://www.some-fictitious-zoo.com/rdf#species"/>
+</treerow>
+</treeitem>
+</treechildren>
+</template>
+</tree>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_treeelementtreecellsortascending.xul b/dom/xul/templates/tests/chrome/test_tmpl_treeelementtreecellsortascending.xul
new file mode 100644
index 000000000..da6c52507
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_treeelementtreecellsortascending.xul
@@ -0,0 +1,133 @@
+<?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"?>
+
+<!--
+ tree element - treecell sort ascending
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols id="treecols" orient="horizontal">
+ <treecol id="treecol" primary="true" label="Name" ordinal="1" sort="rdf:http://www.some-fictitious-zoo.com/rdf#name" sortDirection="ascending" sortActive="true"/>
+ <treecol label="Species" ordinal="3"/>
+ </treecols>
+ <treechildren>
+ <treeitem step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx">
+ <treerow>
+ <treecell label="Archaeopteryx"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl">
+ <treerow>
+ <treecell label="Barn Owl"/>
+ <treecell label="Tyto alba"/>
+ </treerow>
+ </treeitem>
+ <treeitem step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin">
+ <treerow>
+ <treecell label="Emperor Penguin"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/emu">
+ <treerow>
+ <treecell label="Emu"/>
+ <treecell label="Dromaius novaehollandiae"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/raven">
+ <treerow>
+ <treecell label="Raven"/>
+ <treecell label="Corvus corax"/>
+ </treerow>
+ </treeitem>
+ <treeitem step="3" id="http://www.some-fictitious-zoo.com/birds/wren">
+ <treerow>
+ <treecell label="Wren"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="tree element - treecell sort ascending";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<tree xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" hidevscroll="true" hidehscroll="true" datasources="rdf:null" flags="" sort="rdf:http://www.some-fictitious-zoo.com/rdf#name" sortDirection="ascending" id="root" ref="http://www.some-fictitious-zoo.com/birds">
+<treecols orient="horizontal" id="treecols">
+<treecol id="treecol" primary="true" label="Name" ordinal="1" sort="rdf:http://www.some-fictitious-zoo.com/rdf#name" sortDirection="ascending" sortActive="true"/>
+<treecol label="Species" ordinal="3"/>
+</treecols>
+<template id="template">
+<treechildren id="treechildren">
+<treeitem uri="rdf:*">
+<treerow>
+<treecell label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+<treecell label="rdf:http://www.some-fictitious-zoo.com/rdf#species"/>
+</treerow>
+</treeitem>
+</treechildren>
+</template>
+</tree>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_treeelementtreecellsortascendingtreebuilder.xul b/dom/xul/templates/tests/chrome/test_tmpl_treeelementtreecellsortascendingtreebuilder.xul
new file mode 100644
index 000000000..a4f0e2417
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_treeelementtreecellsortascendingtreebuilder.xul
@@ -0,0 +1,133 @@
+<?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"?>
+
+<!--
+ tree element - treecell sort ascending tree builder
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols id="treecols" orient="horizontal">
+ <treecol id="treecol" primary="true" label="Name" ordinal="1" sort="rdf:http://www.some-fictitious-zoo.com/rdf#name" sortDirection="ascending" sortActive="true"/>
+ <treecol label="Species" ordinal="3"/>
+ </treecols>
+ <treechildren>
+ <treeitem step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx">
+ <treerow>
+ <treecell label="Archaeopteryx"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl">
+ <treerow>
+ <treecell label="Barn Owl"/>
+ <treecell label="Tyto alba"/>
+ </treerow>
+ </treeitem>
+ <treeitem step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin">
+ <treerow>
+ <treecell label="Emperor Penguin"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/emu">
+ <treerow>
+ <treecell label="Emu"/>
+ <treecell label="Dromaius novaehollandiae"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/raven">
+ <treerow>
+ <treecell label="Raven"/>
+ <treecell label="Corvus corax"/>
+ </treerow>
+ </treeitem>
+ <treeitem step="3" id="http://www.some-fictitious-zoo.com/birds/wren">
+ <treerow>
+ <treecell label="Wren"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="tree element - treecell sort ascending tree builder";
+var queryType = "rdf";
+var isTreeBuilder = true;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<tree xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" hidevscroll="true" hidehscroll="true" datasources="rdf:null" flags="dont-build-content" sort="rdf:http://www.some-fictitious-zoo.com/rdf#name" sortDirection="ascending" id="root" ref="http://www.some-fictitious-zoo.com/birds">
+<treecols orient="horizontal" id="treecols">
+<treecol id="treecol" primary="true" label="Name" ordinal="1" sort="rdf:http://www.some-fictitious-zoo.com/rdf#name" sortDirection="ascending" sortActive="true"/>
+<treecol label="Species" ordinal="3"/>
+</treecols>
+<template id="template">
+<treechildren id="treechildren">
+<treeitem uri="rdf:*">
+<treerow>
+<treecell label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+<treecell label="rdf:http://www.some-fictitious-zoo.com/rdf#species"/>
+</treerow>
+</treeitem>
+</treechildren>
+</template>
+</tree>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_treeelementtreecelltreebuilder.xul b/dom/xul/templates/tests/chrome/test_tmpl_treeelementtreecelltreebuilder.xul
new file mode 100644
index 000000000..dcc14e0bb
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_treeelementtreecelltreebuilder.xul
@@ -0,0 +1,133 @@
+<?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"?>
+
+<!--
+ tree element - treecell tree builder
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols id="treecols" orient="horizontal">
+ <treecol id="treecol" primary="true" label="Name" ordinal="1"/>
+ <treecol label="Species" ordinal="3"/>
+ </treecols>
+ <treechildren>
+ <treeitem step="3" id="http://www.some-fictitious-zoo.com/birds/wren">
+ <treerow>
+ <treecell label="Wren"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/emu">
+ <treerow>
+ <treecell label="Emu"/>
+ <treecell label="Dromaius novaehollandiae"/>
+ </treerow>
+ </treeitem>
+ <treeitem step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl">
+ <treerow>
+ <treecell label="Barn Owl"/>
+ <treecell label="Tyto alba"/>
+ </treerow>
+ </treeitem>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/raven">
+ <treerow>
+ <treecell label="Raven"/>
+ <treecell label="Corvus corax"/>
+ </treerow>
+ </treeitem>
+ <treeitem step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx">
+ <treerow>
+ <treecell label="Archaeopteryx"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ <treeitem step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin">
+ <treerow>
+ <treecell label="Emperor Penguin"/>
+ <treecell/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="tree element - treecell tree builder";
+var queryType = "rdf";
+var isTreeBuilder = true;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = true;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<tree xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" hidevscroll="true" hidehscroll="true" datasources="rdf:null" flags="dont-build-content" id="root" ref="http://www.some-fictitious-zoo.com/birds">
+<treecols orient="horizontal" id="treecols">
+<treecol id="treecol" primary="true" label="Name" ordinal="1"/>
+<treecol label="Species" ordinal="3"/>
+</treecols>
+<template id="template">
+<treechildren id="treechildren">
+<treeitem uri="rdf:*">
+<treerow>
+<treecell label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+<treecell label="rdf:http://www.some-fictitious-zoo.com/rdf#species"/>
+</treerow>
+</treeitem>
+</treechildren>
+</template>
+</tree>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_treeelementtreeitemonly.xul b/dom/xul/templates/tests/chrome/test_tmpl_treeelementtreeitemonly.xul
new file mode 100644
index 000000000..fb94cbdad
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_treeelementtreeitemonly.xul
@@ -0,0 +1,96 @@
+<?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"?>
+
+<!--
+ tree element - treeitem only
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols id="treecols" orient="horizontal">
+ <treecol id="treecol" primary="true" label="Name" ordinal="1"/>
+ </treecols>
+ <treechildren>
+ <treeitem step="3" id="http://www.some-fictitious-zoo.com/birds/wren" label="Wren"/>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/emu" label="Emu"/>
+ <treeitem step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl" label="Barn Owl"/>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/raven" label="Raven"/>
+ <treeitem step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx" label="Archaeopteryx"/>
+ <treeitem step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" label="Emperor Penguin"/>
+ </treechildren>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="tree element - treeitem only";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<tree xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds" hidevscroll="true" hidehscroll="true">
+<treecols orient="horizontal" id="treecols">
+<treecol id="treecol" primary="true" label="Name"/>
+</treecols>
+<template id="template">
+<treechildren id="treechildren">
+<treeitem uri="rdf:*" label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</treechildren>
+</template>
+</tree>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_treeelementtreeitemsortascending.xul b/dom/xul/templates/tests/chrome/test_tmpl_treeelementtreeitemsortascending.xul
new file mode 100644
index 000000000..654e79e1c
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_treeelementtreeitemsortascending.xul
@@ -0,0 +1,96 @@
+<?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"?>
+
+<!--
+ tree element - treeitem sort ascending
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <treecols id="treecols" orient="horizontal">
+ <treecol id="treecol" primary="true" label="Name" ordinal="1" sort="rdf:http://www.some-fictitious-zoo.com/rdf#name" sortDirection="ascending" sortActive="true"/>
+ </treecols>
+ <treechildren>
+ <treeitem step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx" label="Archaeopteryx"/>
+ <treeitem step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl" label="Barn Owl"/>
+ <treeitem step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" label="Emperor Penguin"/>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/emu" label="Emu"/>
+ <treeitem id="http://www.some-fictitious-zoo.com/birds/raven" label="Raven"/>
+ <treeitem step="3" id="http://www.some-fictitious-zoo.com/birds/wren" label="Wren"/>
+ </treechildren>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="tree element - treeitem sort ascending";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<tree xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" hidevscroll="true" hidehscroll="true" datasources="rdf:null" sort="rdf:http://www.some-fictitious-zoo.com/rdf#name" sortDirection="ascending" id="root" ref="http://www.some-fictitious-zoo.com/birds">
+<treecols orient="horizontal" id="treecols">
+<treecol id="treecol" primary="true" label="Name" ordinal="1" sort="rdf:http://www.some-fictitious-zoo.com/rdf#name" sortDirection="ascending" sortActive="true"/>
+</treecols>
+<template id="template">
+<treechildren id="treechildren">
+<treeitem uri="rdf:*" label="rdf:http://www.some-fictitious-zoo.com/rdf#name"/>
+</treechildren>
+</template>
+</tree>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_twogenerationnodes.xul b/dom/xul/templates/tests/chrome/test_tmpl_twogenerationnodes.xul
new file mode 100644
index 000000000..17528d34b
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_twogenerationnodes.xul
@@ -0,0 +1,100 @@
+<?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"?>
+
+<!--
+ two generation nodes
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <button step="3" id="http://www.some-fictitious-zoo.com/birds/wren" label="Wren"/>
+ <button step="3" id="http://www.some-fictitious-zoo.com/birds/wren" label="Wren"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/emu" label="Emu"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/emu" label="Emu"/>
+ <button step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl" label="Barn Owl"/>
+ <button step="-4" id="http://www.some-fictitious-zoo.com/birds/barnowl" label="Barn Owl"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/raven" label="Raven"/>
+ <button id="http://www.some-fictitious-zoo.com/birds/raven" label="Raven"/>
+ <button step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx" label="Archaeopteryx"/>
+ <button step="2" id="http://www.some-fictitious-zoo.com/birds/archaeopteryx" label="Archaeopteryx"/>
+ <button step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" label="Emperor Penguin"/>
+ <button step="1" id="http://www.some-fictitious-zoo.com/birds/emperorpenguin" label="Emperor Penguin"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="two generation nodes";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" ref="http://www.some-fictitious-zoo.com/birds">
+<template>
+<query>
+<content uri="?uri"/>
+<member container="?uri" child="?child"/>
+<triple subject="?child" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+</query>
+<action>
+<button uri="?child" label="?name"/>
+<button uri="?child" label="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereafterignorecase.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereafterignorecase.xul
new file mode 100644
index 000000000..1fda7ec75
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereafterignorecase.xul
@@ -0,0 +1,60 @@
+<?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"?>
+
+<!--
+ where - after ignore case
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - after ignore case";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="after" ignorecase="true" value="l"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereafterlowercase.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereafterlowercase.xul
new file mode 100644
index 000000000..987c9a468
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereafterlowercase.xul
@@ -0,0 +1,63 @@
+<?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"?>
+
+<!--
+ where - after lowercase
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - after lowercase";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = true;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="after" value="l"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereafternegation.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereafternegation.xul
new file mode 100644
index 000000000..4ae159991
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereafternegation.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ where - after negation
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - after negation";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="after" negate="true" value="H"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereafteruppercase.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereafteruppercase.xul
new file mode 100644
index 000000000..4f11a3de3
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereafteruppercase.xul
@@ -0,0 +1,60 @@
+<?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"?>
+
+<!--
+ where - after uppercase
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - after uppercase";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="after" value="L"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherebeforeignorecase.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherebeforeignorecase.xul
new file mode 100644
index 000000000..130f3ee76
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherebeforeignorecase.xul
@@ -0,0 +1,60 @@
+<?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"?>
+
+<!--
+ where - before ignore case
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - before ignore case";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = true;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="before" ignorecase="true" value="h"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherebeforelowercase.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherebeforelowercase.xul
new file mode 100644
index 000000000..b6a6a1de3
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherebeforelowercase.xul
@@ -0,0 +1,64 @@
+<?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"?>
+
+<!--
+ where - before lowercase
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - before lowercase";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = true;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="before" value="l"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherebeforenegation.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherebeforenegation.xul
new file mode 100644
index 000000000..9885dbfce
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherebeforenegation.xul
@@ -0,0 +1,58 @@
+<?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"?>
+
+<!--
+ where - before negation
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - before negation";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="before" negate="true" value="N"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherebeforeuppercase.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherebeforeuppercase.xul
new file mode 100644
index 000000000..40cd73854
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherebeforeuppercase.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ where - before uppercase
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - before uppercase";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="before" value="H"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherecontains.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherecontains.xul
new file mode 100644
index 000000000..09818b1e1
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherecontains.xul
@@ -0,0 +1,113 @@
+<?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"?>
+
+<!--
+ where - contains
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="3" id="http://www.some-fictitious-zoo.com/mammals/zebra" value="Zebra"/>
+ <label step="-4" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/mammals/arctichare" value="Arctic Hare"/>
+ <label step="5" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - contains";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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="rdf:null" 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">
+<where subject="?name" rel="contains" value="e"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherecontainsignorecase.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherecontainsignorecase.xul
new file mode 100644
index 000000000..ecec06997
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherecontainsignorecase.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ where - contains ignore case
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - contains ignore case";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="contains" ignorecase="true" value="P"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherecontainsnegation.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherecontainsnegation.xul
new file mode 100644
index 000000000..07a421ef0
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherecontainsnegation.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ where - contains negation
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - contains negation";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="contains" negate="true" value="a"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherecontainsnumber.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherecontainsnumber.xul
new file mode 100644
index 000000000..b1934edca
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherecontainsnumber.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ where - contains number
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="2"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="20"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="2"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - contains number";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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#specimens" object="?specimens"/>
+</query>
+<rule>
+<conditions id="conditions">
+<where subject="?specimens" rel="contains" value="2"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?specimens"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherecontainsnumberstring.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherecontainsnumberstring.xul
new file mode 100644
index 000000000..033c87506
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherecontainsnumberstring.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ where - contains number string
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="2"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="20"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="2"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - contains number string";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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#specimensAsString" object="?specimens"/>
+</query>
+<rule>
+<conditions id="conditions">
+<where subject="?specimens" rel="contains" value="2"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?specimens"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherecontainsresource.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherecontainsresource.xul
new file mode 100644
index 000000000..45569d41e
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherecontainsresource.xul
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ where - contains resource
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - contains resource";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?animal" rel="contains" value="/llama"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherecontainstwo.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherecontainstwo.xul
new file mode 100644
index 000000000..1519862fa
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherecontainstwo.xul
@@ -0,0 +1,113 @@
+<?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"?>
+
+<!--
+ where - contains two
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="-4" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/mammals/arctichare" value="Arctic Hare"/>
+ <label step="5" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - contains two";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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="rdf:null" 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">
+<where subject="?name" rel="contains" value="i"/>
+<where subject="?name" rel="contains" value="r"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereendswith.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereendswith.xul
new file mode 100644
index 000000000..69a6d68fc
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereendswith.xul
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ where - endswith
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - endswith";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="endswith" value="a"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereendswithignorecase.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereendswithignorecase.xul
new file mode 100644
index 000000000..34794c51a
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereendswithignorecase.xul
@@ -0,0 +1,58 @@
+<?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"?>
+
+<!--
+ where - endswith ignore case
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - endswith ignore case";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="endswith" ignorecase="true" value="A"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereendswithnegation.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereendswithnegation.xul
new file mode 100644
index 000000000..8d3d0c575
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereendswithnegation.xul
@@ -0,0 +1,63 @@
+<?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"?>
+
+<!--
+ where - endswith negation
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - endswith negation";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="endswith" negate="true" value="k"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereequals.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereequals.xul
new file mode 100644
index 000000000..2679c607e
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereequals.xul
@@ -0,0 +1,109 @@
+<?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"?>
+
+<!--
+ where - equals
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="-4" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label step="5" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - equals";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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="rdf:null" 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">
+<where subject="?name" rel="equals" value="African Elephant"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereequalsignorecase.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsignorecase.xul
new file mode 100644
index 000000000..e26be898b
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsignorecase.xul
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ where - equals ignore case
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - equals ignore case";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="equals" ignorecase="true" value="GoriLLA"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereequalsmultiple.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsmultiple.xul
new file mode 100644
index 000000000..00558388d
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsmultiple.xul
@@ -0,0 +1,58 @@
+<?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"?>
+
+<!--
+ where - equals multiple
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - equals multiple";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="equals" multiple="true" value="Lion,Gorilla"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereequalsmultiplenegation.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsmultiplenegation.xul
new file mode 100644
index 000000000..a66792dd7
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsmultiplenegation.xul
@@ -0,0 +1,125 @@
+<?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"?>
+
+<!--
+ where - equals multiple negation
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="3" id="http://www.some-fictitious-zoo.com/mammals/zebra" value="Zebra"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label step="-4" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label step="1" id="http://www.some-fictitious-zoo.com/mammals/arctichare" value="Arctic Hare"/>
+ <label step="5" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - equals multiple negation";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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" flags="logging"
+ datasources="rdf:null" 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">
+<where subject="?name" rel="equals" multiple="true" negate="true" value="Lion,aardvark,LLAMA,Gorilla,Koala"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereequalsmultiplenegationignorecase.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsmultiplenegationignorecase.xul
new file mode 100644
index 000000000..67b3f2fcf
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsmultiplenegationignorecase.xul
@@ -0,0 +1,60 @@
+<?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"?>
+
+<!--
+ where - equals multiple negation ignore case
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - equals multiple negation ignore case";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="equals" multiple="true" ignorecase="true" negate="true" value="Lion,Aardvark,llama,Polar BEAR"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereequalsnegation.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsnegation.xul
new file mode 100644
index 000000000..edcac5542
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsnegation.xul
@@ -0,0 +1,63 @@
+<?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"?>
+
+<!--
+ where - equals negation
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - equals negation";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="equals" negate="true" value="Polar Bear"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereequalsnegationignorecase.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsnegationignorecase.xul
new file mode 100644
index 000000000..cd877f1ce
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsnegationignorecase.xul
@@ -0,0 +1,63 @@
+<?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"?>
+
+<!--
+ where - equals negation ignore case
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - equals negation ignore case";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="equals" negate="true" ignorecase="true" value="AFRICAN Elephant"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereequalsnegationwrongcase.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsnegationwrongcase.xul
new file mode 100644
index 000000000..16eca331e
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsnegationwrongcase.xul
@@ -0,0 +1,64 @@
+<?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"?>
+
+<!--
+ where - equals negation wrong case
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - equals negation wrong case";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="equals" negate="true" value="Llama"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereequalsnumber.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsnumber.xul
new file mode 100644
index 000000000..c036a3c6a
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsnumber.xul
@@ -0,0 +1,58 @@
+<?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"?>
+
+<!--
+ where - equals number
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="2"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="2"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - equals number";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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#specimens" object="?specimens"/>
+</query>
+<rule>
+<conditions id="conditions">
+<where subject="?specimens" rel="equals" value="2"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?specimens"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereequalsothervariable.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsothervariable.xul
new file mode 100644
index 000000000..a6ae12135
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsothervariable.xul
@@ -0,0 +1,65 @@
+<?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"?>
+
+<!--
+ where - equals other variable
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - equals other variable";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?othername"/>
+</query>
+<rule id="rule">
+<conditions id="conditions">
+<where subject="?name" rel="equals" value="?othername"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereequalsresource.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsresource.xul
new file mode 100644
index 000000000..d2779a20f
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereequalsresource.xul
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ where - equals resource
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - equals resource";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?animal" rel="equals" value="http://www.some-fictitious-zoo.com/mammals/polarbear"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereequalssamevariable.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereequalssamevariable.xul
new file mode 100644
index 000000000..455fd147f
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereequalssamevariable.xul
@@ -0,0 +1,64 @@
+<?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"?>
+
+<!--
+ where - equals same variable
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - equals same variable";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="equals" value="?name"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereequalswrongcase.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereequalswrongcase.xul
new file mode 100644
index 000000000..b67200b57
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereequalswrongcase.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ where - equals wrong case
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output"/>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - equals wrong case";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="equals" value="Llama"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wheregreater.xul b/dom/xul/templates/tests/chrome/test_tmpl_wheregreater.xul
new file mode 100644
index 000000000..5276fb598
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wheregreater.xul
@@ -0,0 +1,60 @@
+<?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"?>
+
+<!--
+ where - greater
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="14"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="5"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="20"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="7"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - greater";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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#specimens" object="?specimens"/>
+</query>
+<rule>
+<conditions id="conditions">
+<where subject="?specimens" rel="greater" value="4"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?specimens"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wheregreaternegation.xul b/dom/xul/templates/tests/chrome/test_tmpl_wheregreaternegation.xul
new file mode 100644
index 000000000..7daa0a7cc
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wheregreaternegation.xul
@@ -0,0 +1,60 @@
+<?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"?>
+
+<!--
+ where - greater negation
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="4"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="2"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="2"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="1"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - greater negation";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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#specimens" object="?specimens"/>
+</query>
+<rule>
+<conditions id="conditions">
+<where subject="?specimens" rel="greater" negate="true" value="4"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?specimens"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wheregreaternegationstring.xul b/dom/xul/templates/tests/chrome/test_tmpl_wheregreaternegationstring.xul
new file mode 100644
index 000000000..22f351547
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wheregreaternegationstring.xul
@@ -0,0 +1,60 @@
+<?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"?>
+
+<!--
+ where - greater negation string
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="4"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="2"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="2"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="1"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - greater negation string";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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#specimensAsString" object="?specimens"/>
+</query>
+<rule>
+<conditions id="conditions">
+<where subject="?specimens" rel="greater" negate="true" value="4"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?specimens"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wheregreaterstring.xul b/dom/xul/templates/tests/chrome/test_tmpl_wheregreaterstring.xul
new file mode 100644
index 000000000..f7c1e4b11
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wheregreaterstring.xul
@@ -0,0 +1,113 @@
+<?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"?>
+
+<!--
+ where - greater string
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label step="-4" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="14"/>
+ <label step="6" id="http://www.some-fictitious-zoo.com/mammals/koala" value="8"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="5"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="20"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="7"/>
+ <label step="5" id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="14"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - greater string";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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="rdf:null" 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#specimensAsString" object="?specimens"/>
+</query>
+<rule>
+<conditions id="conditions">
+<where subject="?specimens" rel="greater" value="4"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?specimens"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_whereless.xul b/dom/xul/templates/tests/chrome/test_tmpl_whereless.xul
new file mode 100644
index 000000000..4c42f54e2
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_whereless.xul
@@ -0,0 +1,62 @@
+<?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"?>
+
+<!--
+ where - less
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="4"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="2"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="5"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="2"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="1"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="7"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - less";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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#specimens" object="?specimens"/>
+</query>
+<rule>
+<conditions id="conditions">
+<where subject="?specimens" rel="less" value="14"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?specimens"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherelessnegation.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherelessnegation.xul
new file mode 100644
index 000000000..46e9d7819
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherelessnegation.xul
@@ -0,0 +1,61 @@
+<?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"?>
+
+<!--
+ where - less negation
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="4"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="14"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="5"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="20"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="7"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - less negation";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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#specimens" object="?specimens"/>
+</query>
+<rule>
+<conditions id="conditions">
+<where subject="?specimens" rel="less" negate="true" value="4"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?specimens"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherelessnegationstring.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherelessnegationstring.xul
new file mode 100644
index 000000000..728e7ae3d
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherelessnegationstring.xul
@@ -0,0 +1,61 @@
+<?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"?>
+
+<!--
+ where - less negation string
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="4"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="14"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="5"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="20"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="7"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - less negation string";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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#specimensAsString" object="?specimens"/>
+</query>
+<rule>
+<conditions id="conditions">
+<where subject="?specimens" rel="less" negate="true" value="4"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?specimens"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherelessstring.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherelessstring.xul
new file mode 100644
index 000000000..445f5efa1
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherelessstring.xul
@@ -0,0 +1,62 @@
+<?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"?>
+
+<!--
+ where - less string
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="4"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="2"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="5"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="2"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="1"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="7"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - less string";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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#specimensAsString" object="?specimens"/>
+</query>
+<rule>
+<conditions id="conditions">
+<where subject="?specimens" rel="less" value="14"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?specimens"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherenorel.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherenorel.xul
new file mode 100644
index 000000000..b7e79ba18
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherenorel.xul
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ where - no rel
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+expectLoggedMessages = function()
+{
+ 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 = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" value="Lion"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherenosubject.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherenosubject.xul
new file mode 100644
index 000000000..c6b566fbe
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherenosubject.xul
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ where - no subject
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+expectLoggedMessages = function()
+{
+ 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 = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where rel="startswith" value="d"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherenovalue.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherenovalue.xul
new file mode 100644
index 000000000..53507b0a3
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherenovalue.xul
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ where - no value
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+expectLoggedMessages = function()
+{
+ 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 = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="startswith"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherestartswith.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherestartswith.xul
new file mode 100644
index 000000000..6e86c981d
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherestartswith.xul
@@ -0,0 +1,108 @@
+<?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"?>
+
+<!--
+ where - startswith
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - startswith";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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);
+ },
+ // 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="rdf:null" 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">
+<where subject="?name" rel="startswith" value="a"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherestartswithignorecase.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherestartswithignorecase.xul
new file mode 100644
index 000000000..434ba40c4
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherestartswithignorecase.xul
@@ -0,0 +1,58 @@
+<?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"?>
+
+<!--
+ where - startswith ignore case
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - startswith ignore case";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="startswith" ignorecase="true" value="a"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherestartswithmultiple.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherestartswithmultiple.xul
new file mode 100644
index 000000000..4876ed383
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherestartswithmultiple.xul
@@ -0,0 +1,59 @@
+<?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"?>
+
+<!--
+ where - startswith multiple
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - startswith multiple";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="startswith" multiple="true" value="L,A"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherestartswithnegation.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherestartswithnegation.xul
new file mode 100644
index 000000000..4ab12c74d
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherestartswithnegation.xul
@@ -0,0 +1,62 @@
+<?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"?>
+
+<!--
+ where - startswith negation
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - startswith negation";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="startswith" negate="true" value="L"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherestartswithunknownvariable.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherestartswithunknownvariable.xul
new file mode 100644
index 000000000..9fd794b92
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherestartswithunknownvariable.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ where - startswith unknown variable
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output"/>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - startswith unknown variable";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="startswith" value="?unknown"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wherestartswithvariable.xul b/dom/xul/templates/tests/chrome/test_tmpl_wherestartswithvariable.xul
new file mode 100644
index 000000000..41e2be959
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wherestartswithvariable.xul
@@ -0,0 +1,64 @@
+<?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"?>
+
+<!--
+ where - startswith variable
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+ <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - startswith variable";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="?name" rel="startswith" value="?name"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wheresubjectequalsvariable.xul b/dom/xul/templates/tests/chrome/test_tmpl_wheresubjectequalsvariable.xul
new file mode 100644
index 000000000..14c03898c
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wheresubjectequalsvariable.xul
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ where - subject equals variable
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - subject equals variable";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="African Elephant" rel="equals" value="?name"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_wheresubjectstartswithvariable.xul b/dom/xul/templates/tests/chrome/test_tmpl_wheresubjectstartswithvariable.xul
new file mode 100644
index 000000000..dd93aa285
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_wheresubjectstartswithvariable.xul
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ where - subject startswith variable
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="where - subject startswith variable";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="rdf:null" 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">
+<where subject="African Elephantitis" rel="startswith" value="?name"/>
+</conditions>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_xmlquerysimple.xul b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerysimple.xul
new file mode 100644
index 000000000..9a576e370
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerysimple.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"?>
+
+<!--
+ xml query simple
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Chamaeleo-chamaelon" label="Chameleon 2"/>
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Dromaius-novaehollandiae" label="Emu 12"/>
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Tyto-alba" label="Barn Owl 4"/>
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Corvus-corax" label="Raven 0"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="xml query simple";
+var queryType = "xml";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="animals.xml" querytype="xml" ref=".">
+<template expr="class/species">
+<button uri="?" label="?name ?specimens"/>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithassign.xul b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithassign.xul
new file mode 100644
index 000000000..39992b43e
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithassign.xul
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ xml query with assign
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <hbox id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Chamaeleo-chamaelon">
+ <button label="Chameleon"/>
+ <label value="9"/>
+ </hbox>
+ <hbox id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Dromaius-novaehollandiae">
+ <button label="Emu"/>
+ <label value="3"/>
+ </hbox>
+ <hbox id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Tyto-alba">
+ <button label="Barn Owl"/>
+ <label value="8"/>
+ </hbox>
+ <hbox id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Corvus-corax">
+ <button label="Raven"/>
+ <label value="5"/>
+ </hbox>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="xml query with assign";
+var queryType = "xml";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="animals.xml" querytype="xml" ref=".">
+<template>
+<query expr="class/species">
+<assign var="?length" expr="string-length(@name)"/>
+</query>
+<action>
+<hbox uri="?">
+<button label="?name"/>
+<label value="?length"/>
+</hbox>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithassignmentandcondition.xul b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithassignmentandcondition.xul
new file mode 100644
index 000000000..fdbbed321
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithassignmentandcondition.xul
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ xml query with assignment and condition
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <hbox anyid="true" container="true" empty="false">
+ <label value="class"/>
+ <hbox anyid="true" container="true" empty="true">
+ <label value="name"/>
+ </hbox>
+ </hbox>
+ <hbox anyid="true" container="true" empty="false">
+ <label value="class"/>
+ <hbox anyid="true" container="true" empty="true">
+ <label value="name"/>
+ </hbox>
+ <hbox anyid="true" container="true" empty="true">
+ <label value="location"/>
+ </hbox>
+ </hbox>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="xml query with assignment and condition";
+var queryType = "xml";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="animals.xml" querytype="xml" ref=".">
+<template>
+<query>
+<assign var="?name" expr="name()"/>
+</query>
+<rule>
+<where subject="?name" rel="equals" negate="true" value="species"/>
+<action>
+<hbox uri="?">
+<label value="?name"/>
+</hbox>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithassignmentandconditiondontrecurse.xul b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithassignmentandconditiondontrecurse.xul
new file mode 100644
index 000000000..8f7e747c3
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithassignmentandconditiondontrecurse.xul
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ xml query with assignment and condition dont-recurse
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <hbox anyid="true" container="true" empty="false">
+ <label value="class"/>
+ </hbox>
+ <hbox anyid="true" container="true" empty="false">
+ <label value="class"/>
+ </hbox>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="xml query with assignment and condition dont-recurse";
+var queryType = "xml";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="animals.xml" querytype="xml" ref="." flags="dont-recurse">
+<template>
+<query>
+<assign var="?name" expr="name()"/>
+</query>
+<rule>
+<where subject="?name" rel="equals" negate="true" value="species"/>
+<action>
+<hbox uri="?">
+<label value="?name"/>
+</hbox>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithbindinginbindings.xul b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithbindinginbindings.xul
new file mode 100644
index 000000000..90372f62c
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithbindinginbindings.xul
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ xml query with binding in bindings
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <checkbox anyid="true" container="true" empty="true" label="Reptiles"/>
+ <checkbox anyid="true" container="true" empty="true" label="Birds"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="xml query with binding in bindings";
+var queryType = "xml";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="animals.xml" querytype="xml" ref=".">
+<template>
+<query expr="class/name"/>
+<rule id="rule">
+<bindings>
+<binding subject="?" predicate="text()" object="?nm"/>
+</bindings>
+<action>
+<checkbox uri="?" label="?nm"/>
+</action>
+</rule>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithbindinginrule.xul b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithbindinginrule.xul
new file mode 100644
index 000000000..57ca2d838
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithbindinginrule.xul
@@ -0,0 +1,64 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ xml query with binding in rule
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <label anyid="true" container="true" empty="true" value="Class Reptiles"/>
+ <label anyid="true" container="true" empty="true" value="Class Birds"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="xml query with binding in rule";
+var queryType = "xml";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+Components.classes["@mozilla.org/consoleservice;1"].
+ getService(Components.interfaces.nsIConsoleService).reset();
+
+expectLoggedMessages = function()
+{
+ // 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"
+ 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>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithdifferentmember.xul b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithdifferentmember.xul
new file mode 100644
index 000000000..160d4bddb
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithdifferentmember.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"?>
+
+<!--
+ xml query with different member
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Chamaeleo-chamaelon" label="Chameleon 2"/>
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Dromaius-novaehollandiae" label="Emu 12"/>
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Tyto-alba" label="Barn Owl 4"/>
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Corvus-corax" label="Raven 0"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="xml query with different member";
+var queryType = "xml";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="animals.xml" querytype="xml" ref=".">
+<template expr="class/species">
+<button uri="?memb" label="?name ?specimens"/>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithinlinedata.xul b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithinlinedata.xul
new file mode 100644
index 000000000..010a6ba49
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithinlinedata.xul
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ xml query with inline data
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <button anyid="true" label="Nathan"/>
+ <button anyid="true" label="Marie"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="xml query with inline data";
+var queryType = "xml";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<people id="data" xmlns:test="http://www.mozilla.com/test">
+<test:person name="Nathan"/>
+<person name="Luke"/>
+<xt:person xmlns:xt="http://www.mozilla.com/test" name="Marie"/>
+</people>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="#data" querytype="xml" ref=".">
+<template expr="test:person">
+<button uri="?" label="?name"/>
+</template>
+<button id="row6" label="Nathan"/>
+<button id="row7" label="Marie"/>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithinlinedatawithmultiplequeries.xul b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithinlinedatawithmultiplequeries.xul
new file mode 100644
index 000000000..d9640fbde
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithinlinedatawithmultiplequeries.xul
@@ -0,0 +1,71 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ xml query with inline data with multiple queries
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <button anyid="true" label="Nathan"/>
+ <button anyid="true" label="Marie"/>
+ <checkbox anyid="true" label="Nathan"/>
+ <checkbox anyid="true" label="Luke"/>
+ <checkbox anyid="true" label="Marie"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="xml query with inline data with multiple queries";
+var queryType = "xml";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<people id="data" xmlns:test="http://www.mozilla.com/test">
+<test:person name="Nathan"/>
+<person name="Luke"/>
+<xt:person xmlns:xt="http://www.mozilla.com/test" name="Marie"/>
+</people>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="#data" querytype="xml" ref=".">
+<template>
+<queryset>
+<query expr="test:person"/>
+<action>
+<button uri="?" label="?name"/>
+</action>
+</queryset>
+<queryset>
+<query/>
+<action>
+<checkbox uri="?" label="?name"/>
+</action>
+</queryset>
+</template>
+<button id="row9" label="Nathan"/>
+<button id="row10" label="Marie"/>
+<checkbox id="row11" label="Nathan"/>
+<checkbox id="row12" label="Luke"/>
+<checkbox id="row13" label="Marie"/>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithmultiplequeries.xul b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithmultiplequeries.xul
new file mode 100644
index 000000000..075d2c453
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithmultiplequeries.xul
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ xml query with multiple queries
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Dromaius-novaehollandiae" label="(Dromaius-novaehollandiae) is a large bird"/>
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Tyto-alba" label="(Tyto-alba) Barn Owl"/>
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Corvus-corax" label="(Corvus-corax) Raven"/>
+ <label id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Chamaeleo-chamaelon" value="Chameleon"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="xml query with multiple queries";
+var queryType = "xml";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="animals.xml" querytype="xml" ref=".">
+<template>
+<queryset>
+<query expr="class[position()&gt;1]/species">
+<assign var="?id" expr="concat('(', @id, ')')"/>
+</query>
+<rule>
+<where subject="?id" rel="equals" value="(Dromaius-novaehollandiae)"/>
+<action>
+<button uri="?" label="?id is a large bird"/>
+</action>
+</rule>
+<rule>
+<binding subject="?" predicate="@name" object="?name"/>
+<action>
+<button uri="?" label="?id ?name"/>
+</action>
+</rule>
+</queryset>
+<queryset>
+<query expr="class[name/text()='Reptiles']/species"/>
+<action>
+<label uri="?" value="?name"/>
+</action>
+</queryset>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithothertypes.xul b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithothertypes.xul
new file mode 100644
index 000000000..ce728b2b1
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithothertypes.xul
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ xml query with other types
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <groupbox anyid="true" container="true" empty="false">
+ <caption label="Reptiles false"/>
+ <label id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Chamaeleo-chamaelon" value="Chameleon"/>
+ </groupbox>
+ <groupbox anyid="true" container="true" empty="false">
+ <caption label="Birds true"/>
+ <label id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Dromaius-novaehollandiae" value="Emu"/>
+ <label id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Tyto-alba" value="Barn Owl"/>
+ <label id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Corvus-corax" value="Raven"/>
+ </groupbox>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="xml query with other types";
+var queryType = "xml";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="animals.xml" querytype="xml" ref=".">
+<template>
+<queryset>
+<query expr="species">
+<assign var="?haschild" expr="boolean(location)"/>
+<assign var="?name" expr="@name"/>
+</query>
+<action>
+<label uri="?" value="?name"/>
+</action>
+</queryset>
+<queryset>
+<query>
+<assign var="?name" expr="name/text()"/>
+<assign var="?isbirds" expr="name/text() = 'Birds'"/>
+</query>
+<rule parent="vbox">
+<action>
+<groupbox uri="?">
+<caption label="?name ?isbirds"/>
+</groupbox>
+</action>
+</rule>
+</queryset>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithsort.xul b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithsort.xul
new file mode 100644
index 000000000..b83fc3595
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithsort.xul
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ xml query with sort
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Tyto-alba" label="Barn Owl"/>
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Chamaeleo-chamaelon" label="Chameleon"/>
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Dromaius-novaehollandiae" label="Emu"/>
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Corvus-corax" label="Raven"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="xml query with sort";
+var queryType = "xml";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="animals.xml" querytype="xml" ref="." sort="?name" sortDirection="ascending">
+<template>
+<query expr="class/species"/>
+<action>
+<button uri="?" label="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
diff --git a/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithsortotherfield.xul b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithsortotherfield.xul
new file mode 100644
index 000000000..ad1829d0d
--- /dev/null
+++ b/dom/xul/templates/tests/chrome/test_tmpl_xmlquerywithsortotherfield.xul
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+ xml query with sort other field
+-->
+
+<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/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <data id="output">
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Tyto-alba" label="Barn Owl"/>
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Dromaius-novaehollandiae" label="Emu"/>
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Corvus-corax" label="Raven"/>
+ <button id="chrome://mochitests/content/chrome/dom/xul/templates/tests/chrome/animals.xml#Chamaeleo-chamaelon" label="Chameleon"/>
+ </data>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="xml query with sort other field";
+var queryType = "xml";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput = document.getElementById("output");
+
+var changes = [];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="animals.xml" querytype="xml" ref="." sort="?id" sortDirection="descending">
+<template>
+<query expr="class/species"/>
+<action>
+<button uri="?" label="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>