summaryrefslogtreecommitdiff
path: root/dom/xul
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
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloaduxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/xul')
-rw-r--r--dom/xul/XULDocument.cpp4591
-rw-r--r--dom/xul/XULDocument.h791
-rw-r--r--dom/xul/crashtests/107518-1.xml52
-rw-r--r--dom/xul/crashtests/252448-1.xul10
-rw-r--r--dom/xul/crashtests/253479-1.xul6
-rw-r--r--dom/xul/crashtests/253479-2.xul4
-rw-r--r--dom/xul/crashtests/326204-1.xul0
-rw-r--r--dom/xul/crashtests/326644-1-inner.xul34
-rw-r--r--dom/xul/crashtests/326644-1.html9
-rw-r--r--dom/xul/crashtests/326644-2-inner.xul10
-rw-r--r--dom/xul/crashtests/326644-2.html9
-rw-r--r--dom/xul/crashtests/326864-1.xul34
-rw-r--r--dom/xul/crashtests/326875-1.xul27
-rw-r--r--dom/xul/crashtests/326881-1.xul13
-rw-r--r--dom/xul/crashtests/329982-1.xhtml42
-rw-r--r--dom/xul/crashtests/336096-1.xhtml41
-rw-r--r--dom/xul/crashtests/344215-1.xul7
-rw-r--r--dom/xul/crashtests/354611-1.html20
-rw-r--r--dom/xul/crashtests/360078-1.xhtml42
-rw-r--r--dom/xul/crashtests/360078-1xbl.xml3
-rw-r--r--dom/xul/crashtests/363791-1.xul44
-rw-r--r--dom/xul/crashtests/384740-1.xul23
-rw-r--r--dom/xul/crashtests/384877-1-inner.xul15
-rw-r--r--dom/xul/crashtests/384877-1.html9
-rw-r--r--dom/xul/crashtests/386914-1-inner.xul10
-rw-r--r--dom/xul/crashtests/386914-1.html9
-rw-r--r--dom/xul/crashtests/386947-1.xul12
-rw-r--r--dom/xul/crashtests/425821-1.xul15
-rw-r--r--dom/xul/crashtests/428951-1.xul21
-rw-r--r--dom/xul/crashtests/429085-1.xhtml21
-rw-r--r--dom/xul/crashtests/431906-1-inner.xul19
-rw-r--r--dom/xul/crashtests/431906-1.html9
-rw-r--r--dom/xul/crashtests/451311-1.xul1
-rw-r--r--dom/xul/crashtests/461917-1.xhtml6
-rw-r--r--dom/xul/crashtests/468211-1.xul23
-rw-r--r--dom/xul/crashtests/468211-2-binding.xml12
-rw-r--r--dom/xul/crashtests/468211-2.xul10
-rw-r--r--dom/xul/crashtests/468211-3.xul18
-rw-r--r--dom/xul/crashtests/495635-1.xul8
-rw-r--r--dom/xul/crashtests/509719-1-overlay.xul3
-rw-r--r--dom/xul/crashtests/509719-1.xul3
-rw-r--r--dom/xul/crashtests/509719-2-overlay.xul8
-rw-r--r--dom/xul/crashtests/509719-2.xul7
-rw-r--r--dom/xul/crashtests/583230.xul23
-rw-r--r--dom/xul/crashtests/crashtests.list33
-rw-r--r--dom/xul/crashtests/extA1.xul8
-rw-r--r--dom/xul/crashtests/extA2.xul10
-rw-r--r--dom/xul/crashtests/extB1.xul5
-rw-r--r--dom/xul/moz.build59
-rw-r--r--dom/xul/nsForwardReference.h76
-rw-r--r--dom/xul/nsIController.idl64
-rw-r--r--dom/xul/nsIControllers.idl34
-rw-r--r--dom/xul/nsIXULDocument.h92
-rw-r--r--dom/xul/nsIXULOverlayProvider.idl34
-rw-r--r--dom/xul/nsXULCommandDispatcher.cpp459
-rw-r--r--dom/xul/nsXULCommandDispatcher.h73
-rw-r--r--dom/xul/nsXULContentSink.cpp1051
-rw-r--r--dom/xul/nsXULContentSink.h150
-rw-r--r--dom/xul/nsXULControllers.cpp246
-rw-r--r--dom/xul/nsXULControllers.h71
-rw-r--r--dom/xul/nsXULElement.cpp2975
-rw-r--r--dom/xul/nsXULElement.h707
-rw-r--r--dom/xul/nsXULPopupListener.cpp442
-rw-r--r--dom/xul/nsXULPopupListener.h61
-rw-r--r--dom/xul/nsXULPrototypeCache.cpp599
-rw-r--r--dom/xul/nsXULPrototypeCache.h140
-rw-r--r--dom/xul/nsXULPrototypeDocument.cpp544
-rw-r--r--dom/xul/nsXULPrototypeDocument.h143
-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
-rw-r--r--dom/xul/test/1061864.html8
-rw-r--r--dom/xul/test/398289-resource.xul42
-rw-r--r--dom/xul/test/bug497875-iframe.xul6
-rw-r--r--dom/xul/test/chrome.ini40
-rw-r--r--dom/xul/test/file_bug1069772.xul143
-rw-r--r--dom/xul/test/file_bug1271240.xul82
-rw-r--r--dom/xul/test/file_bug236853.rdf14
-rw-r--r--dom/xul/test/mochitest.ini5
-rw-r--r--dom/xul/test/overlay1_bug335375.xul9
-rw-r--r--dom/xul/test/overlay2_bug335375.xul5
-rw-r--r--dom/xul/test/overlay_640158.xul8
-rw-r--r--dom/xul/test/test_bug1061864_1.xul50
-rw-r--r--dom/xul/test/test_bug1061864_2.xul52
-rw-r--r--dom/xul/test/test_bug1069772.xul32
-rw-r--r--dom/xul/test/test_bug1070049_throw_from_script.xul41
-rw-r--r--dom/xul/test/test_bug1271240.xul38
-rw-r--r--dom/xul/test/test_bug1290965.xul39
-rw-r--r--dom/xul/test/test_bug199692.xul98
-rw-r--r--dom/xul/test/test_bug233643.xul76
-rw-r--r--dom/xul/test/test_bug236853.xul37
-rw-r--r--dom/xul/test/test_bug311681.xul106
-rw-r--r--dom/xul/test/test_bug335375.xul50
-rw-r--r--dom/xul/test/test_bug391002.xul41
-rw-r--r--dom/xul/test/test_bug398289.html40
-rw-r--r--dom/xul/test/test_bug403868.xul83
-rw-r--r--dom/xul/test/test_bug414907.xul53
-rw-r--r--dom/xul/test/test_bug418216.xul46
-rw-r--r--dom/xul/test/test_bug445177.xul85
-rw-r--r--dom/xul/test/test_bug449457.xul25
-rw-r--r--dom/xul/test/test_bug468176.xul84
-rw-r--r--dom/xul/test/test_bug486990.xul155
-rw-r--r--dom/xul/test/test_bug497875.xul53
-rw-r--r--dom/xul/test/test_bug583948.xul38
-rw-r--r--dom/xul/test/test_bug640158_overlay_persist.xul51
-rw-r--r--dom/xul/test/test_bug749367.xul39
-rw-r--r--dom/xul/test/test_bug757137.xul52
-rw-r--r--dom/xul/test/test_bug775972.xul35
-rw-r--r--dom/xul/test/test_import_xul_to_content.xul69
-rw-r--r--dom/xul/test/window_bug583948.xul8
-rw-r--r--dom/xul/test/window_bug757137.xul6
398 files changed, 53747 insertions, 0 deletions
diff --git a/dom/xul/XULDocument.cpp b/dom/xul/XULDocument.cpp
new file mode 100644
index 0000000000..ae3cdb7eb9
--- /dev/null
+++ b/dom/xul/XULDocument.cpp
@@ -0,0 +1,4591 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 sw=4 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+
+ An implementation for the XUL document. This implementation serves
+ as the basis for generating an NGLayout content model.
+
+ Notes
+ -----
+
+ 1. We do some monkey business in the document observer methods to
+ keep the element map in sync for HTML elements. Why don't we just
+ do it for _all_ elements? Well, in the case of XUL elements,
+ which may be lazily created during frame construction, the
+ document observer methods will never be called because we'll be
+ adding the XUL nodes into the content model "quietly".
+
+*/
+
+#include "mozilla/ArrayUtils.h"
+
+#include "XULDocument.h"
+
+#include "nsError.h"
+#include "nsIBoxObject.h"
+#include "nsIChromeRegistry.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsIContentViewer.h"
+#include "nsIDOMXULElement.h"
+#include "nsIStreamListener.h"
+#include "nsITimer.h"
+#include "nsDocShell.h"
+#include "nsGkAtoms.h"
+#include "nsXMLContentSink.h"
+#include "nsXULContentSink.h"
+#include "nsXULContentUtils.h"
+#include "nsIXULOverlayProvider.h"
+#include "nsIStringEnumerator.h"
+#include "nsNetUtil.h"
+#include "nsParserCIID.h"
+#include "nsPIBoxObject.h"
+#include "mozilla/dom/BoxObject.h"
+#include "nsXPIDLString.h"
+#include "nsPIDOMWindow.h"
+#include "nsPIWindowRoot.h"
+#include "nsXULCommandDispatcher.h"
+#include "nsXULElement.h"
+#include "mozilla/Logging.h"
+#include "rdf.h"
+#include "nsIFrame.h"
+#include "nsXBLService.h"
+#include "nsCExternalHandlerService.h"
+#include "nsMimeTypes.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsContentList.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsNodeInfoManager.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentUtils.h"
+#include "nsIParser.h"
+#include "nsCharsetSource.h"
+#include "nsIParserService.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/css/Loader.h"
+#include "nsIScriptError.h"
+#include "nsIStyleSheetLinkingElement.h"
+#include "nsIObserverService.h"
+#include "nsNodeUtils.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIXULWindow.h"
+#include "nsXULPopupManager.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsURILoader.h"
+#include "mozilla/AddonPathService.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/NodeInfoInlines.h"
+#include "mozilla/dom/ProcessingInstruction.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/XULDocumentBinding.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/Preferences.h"
+#include "nsTextNode.h"
+#include "nsJSUtils.h"
+#include "mozilla/dom/URL.h"
+#include "nsIContentPolicy.h"
+#include "mozAutoDocUpdate.h"
+#include "xpcpublic.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+//----------------------------------------------------------------------
+//
+// CIDs
+//
+
+static NS_DEFINE_CID(kParserCID, NS_PARSER_CID);
+
+static bool IsOverlayAllowed(nsIURI* aURI)
+{
+ bool canOverlay = false;
+ if (NS_SUCCEEDED(aURI->SchemeIs("about", &canOverlay)) && canOverlay)
+ return true;
+ if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &canOverlay)) && canOverlay)
+ return true;
+ return false;
+}
+
+//----------------------------------------------------------------------
+//
+// Miscellaneous Constants
+//
+
+const nsForwardReference::Phase nsForwardReference::kPasses[] = {
+ nsForwardReference::eConstruction,
+ nsForwardReference::eHookup,
+ nsForwardReference::eDone
+};
+
+//----------------------------------------------------------------------
+//
+// Statics
+//
+
+int32_t XULDocument::gRefCnt = 0;
+
+LazyLogModule XULDocument::gXULLog("XULDocument");
+
+//----------------------------------------------------------------------
+
+struct BroadcastListener {
+ nsWeakPtr mListener;
+ nsCOMPtr<nsIAtom> mAttribute;
+};
+
+struct BroadcasterMapEntry : public PLDHashEntryHdr
+{
+ Element* mBroadcaster; // [WEAK]
+ nsTArray<BroadcastListener*> mListeners; // [OWNING] of BroadcastListener objects
+};
+
+Element*
+nsRefMapEntry::GetFirstElement()
+{
+ return mRefContentList.SafeElementAt(0);
+}
+
+void
+nsRefMapEntry::AppendAll(nsCOMArray<nsIContent>* aElements)
+{
+ for (size_t i = 0; i < mRefContentList.Length(); ++i) {
+ aElements->AppendObject(mRefContentList[i]);
+ }
+}
+
+bool
+nsRefMapEntry::AddElement(Element* aElement)
+{
+ if (mRefContentList.Contains(aElement)) {
+ return true;
+ }
+ return mRefContentList.AppendElement(aElement);
+}
+
+bool
+nsRefMapEntry::RemoveElement(Element* aElement)
+{
+ mRefContentList.RemoveElement(aElement);
+ return mRefContentList.IsEmpty();
+}
+
+//----------------------------------------------------------------------
+//
+// ctors & dtors
+//
+
+namespace mozilla {
+namespace dom {
+
+XULDocument::XULDocument(void)
+ : XMLDocument("application/vnd.mozilla.xul+xml"),
+ mDocLWTheme(Doc_Theme_Uninitialized),
+ mState(eState_Master),
+ mResolutionPhase(nsForwardReference::eStart)
+{
+ // NOTE! nsDocument::operator new() zeroes out all members, so don't
+ // bother initializing members to 0.
+
+ // Override the default in nsDocument
+ mCharacterSet.AssignLiteral("UTF-8");
+
+ mDefaultElementType = kNameSpaceID_XUL;
+ mType = eXUL;
+
+ mDelayFrameLoaderInitialization = true;
+
+ mAllowXULXBL = eTriTrue;
+}
+
+XULDocument::~XULDocument()
+{
+ NS_ASSERTION(mNextSrcLoadWaiter == nullptr,
+ "unreferenced document still waiting for script source to load?");
+
+ // In case we failed somewhere early on and the forward observer
+ // decls never got resolved.
+ mForwardReferences.Clear();
+ // Likewise for any references we have to IDs where we might
+ // look for persisted data:
+ mPersistenceIds.Clear();
+
+ // Destroy our broadcaster map.
+ delete mBroadcasterMap;
+
+ delete mTemplateBuilderTable;
+
+ Preferences::UnregisterCallback(XULDocument::DirectionChanged,
+ "intl.uidirection.", this);
+
+ if (mOffThreadCompileStringBuf) {
+ js_free(mOffThreadCompileStringBuf);
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
+
+nsresult
+NS_NewXULDocument(nsIXULDocument** result)
+{
+ NS_PRECONDITION(result != nullptr, "null ptr");
+ if (! result)
+ return NS_ERROR_NULL_POINTER;
+
+ RefPtr<XULDocument> doc = new XULDocument();
+
+ nsresult rv;
+ if (NS_FAILED(rv = doc->Init())) {
+ return rv;
+ }
+
+ doc.forget(result);
+ return NS_OK;
+}
+
+
+namespace mozilla {
+namespace dom {
+
+//----------------------------------------------------------------------
+//
+// nsISupports interface
+//
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(XULDocument)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XULDocument, XMLDocument)
+ NS_ASSERTION(!nsCCUncollectableMarker::InGeneration(cb, tmp->GetMarkedCCGeneration()),
+ "Shouldn't traverse XULDocument!");
+ // XXX tmp->mForwardReferences?
+ // XXX tmp->mContextStack?
+
+ // An element will only have a template builder as long as it's in the
+ // document, so we'll traverse the table here instead of from the element.
+ if (tmp->mTemplateBuilderTable) {
+ for (auto iter = tmp->mTemplateBuilderTable->Iter();
+ !iter.Done();
+ iter.Next()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mTemplateBuilderTable key");
+ cb.NoteXPCOMChild(iter.Key());
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mTemplateBuilderTable value");
+ cb.NoteXPCOMChild(iter.UserData());
+ }
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCurrentPrototype)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMasterPrototype)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypes)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStore)
+
+ if (tmp->mOverlayLoadObservers) {
+ for (auto iter = tmp->mOverlayLoadObservers->Iter();
+ !iter.Done();
+ iter.Next()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mOverlayLoadObservers value");
+ cb.NoteXPCOMChild(iter.Data());
+ }
+ }
+ if (tmp->mPendingOverlayLoadNotifications) {
+ for (auto iter = tmp->mPendingOverlayLoadNotifications->Iter();
+ !iter.Done();
+ iter.Next()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mPendingOverlayLoadNotifications value");
+ cb.NoteXPCOMChild(iter.Data());
+ }
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULDocument, XMLDocument)
+ delete tmp->mTemplateBuilderTable;
+ tmp->mTemplateBuilderTable = nullptr;
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mCommandDispatcher)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStore)
+ //XXX We should probably unlink all the objects we traverse.
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(XULDocument, XMLDocument)
+NS_IMPL_RELEASE_INHERITED(XULDocument, XMLDocument)
+
+
+// QueryInterface implementation for XULDocument
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(XULDocument)
+ NS_INTERFACE_TABLE_INHERITED(XULDocument, nsIXULDocument,
+ nsIDOMXULDocument, nsIStreamLoaderObserver,
+ nsICSSLoaderObserver, nsIOffThreadScriptReceiver)
+NS_INTERFACE_TABLE_TAIL_INHERITING(XMLDocument)
+
+
+//----------------------------------------------------------------------
+//
+// nsIDocument interface
+//
+
+void
+XULDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup)
+{
+ NS_NOTREACHED("Reset");
+}
+
+void
+XULDocument::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
+ nsIPrincipal* aPrincipal)
+{
+ NS_NOTREACHED("ResetToURI");
+}
+
+void
+XULDocument::SetContentType(const nsAString& aContentType)
+{
+ NS_ASSERTION(aContentType.EqualsLiteral("application/vnd.mozilla.xul+xml"),
+ "xul-documents always has content-type application/vnd.mozilla.xul+xml");
+ // Don't do anything, xul always has the mimetype
+ // application/vnd.mozilla.xul+xml
+}
+
+// This is called when the master document begins loading, whether it's
+// being cached or not.
+nsresult
+XULDocument::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ nsISupports* aContainer,
+ nsIStreamListener **aDocListener,
+ bool aReset, nsIContentSink* aSink)
+{
+ if (MOZ_LOG_TEST(gXULLog, LogLevel::Warning)) {
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aChannel->GetOriginalURI(getter_AddRefs(uri));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString urlspec;
+ rv = uri->GetSpec(urlspec);
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_LOG(gXULLog, LogLevel::Warning,
+ ("xul: load document '%s'", urlspec.get()));
+ }
+ }
+ }
+ // NOTE: If this ever starts calling nsDocument::StartDocumentLoad
+ // we'll possibly need to reset our content type afterwards.
+ mStillWalking = true;
+ mMayStartLayout = false;
+ mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
+
+ mChannel = aChannel;
+
+ // Get the URI. Note that this should match nsDocShell::OnLoadingSite
+ nsresult rv =
+ NS_GetFinalChannelURI(aChannel, getter_AddRefs(mDocumentURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ResetStylesheetsToURI(mDocumentURI);
+
+ RetrieveRelevantHeaders(aChannel);
+
+ // Look in the chrome cache: we've got this puppy loaded
+ // already.
+ nsXULPrototypeDocument* proto = IsChromeURI(mDocumentURI) ?
+ nsXULPrototypeCache::GetInstance()->GetPrototype(mDocumentURI) :
+ nullptr;
+
+ // Same comment as nsChromeProtocolHandler::NewChannel and
+ // XULDocument::ResumeWalk
+ // - Ben Goodger
+ //
+ // We don't abort on failure here because there are too many valid
+ // cases that can return failure, and the null-ness of |proto| is enough
+ // to trigger the fail-safe parse-from-disk solution. Example failure cases
+ // (for reference) include:
+ //
+ // NS_ERROR_NOT_AVAILABLE: the URI cannot be found in the startup cache,
+ // parse from disk
+ // other: the startup cache file could not be found, probably
+ // due to being accessed before a profile has been selected (e.g.
+ // loading chrome for the profile manager itself). This must be
+ // parsed from disk.
+
+ if (proto) {
+ // If we're racing with another document to load proto, wait till the
+ // load has finished loading before trying to add cloned style sheets.
+ // XULDocument::EndLoad will call proto->NotifyLoadDone, which will
+ // find all racing documents and notify them via OnPrototypeLoadDone,
+ // which will add style sheet clones to each document.
+ bool loaded;
+ rv = proto->AwaitLoadDone(this, &loaded);
+ if (NS_FAILED(rv)) return rv;
+
+ mMasterPrototype = mCurrentPrototype = proto;
+
+ // Set up the right principal on ourselves.
+ SetPrincipal(proto->DocumentPrincipal());
+
+ // We need a listener, even if proto is not yet loaded, in which
+ // event the listener's OnStopRequest method does nothing, and all
+ // the interesting work happens below XULDocument::EndLoad, from
+ // the call there to mCurrentPrototype->NotifyLoadDone().
+ *aDocListener = new CachedChromeStreamListener(this, loaded);
+ }
+ else {
+ bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
+ bool fillXULCache = (useXULCache && IsChromeURI(mDocumentURI));
+
+
+ // It's just a vanilla document load. Create a parser to deal
+ // with the stream n' stuff.
+
+ nsCOMPtr<nsIParser> parser;
+ rv = PrepareToLoad(aContainer, aCommand, aChannel, aLoadGroup,
+ getter_AddRefs(parser));
+ if (NS_FAILED(rv)) return rv;
+
+ // Predicate mIsWritingFastLoad on the XUL cache being enabled,
+ // so we don't have to re-check whether the cache is enabled all
+ // the time.
+ mIsWritingFastLoad = useXULCache;
+
+ nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(parser, &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "parser doesn't support nsIStreamListener");
+ if (NS_FAILED(rv)) return rv;
+
+ *aDocListener = listener;
+
+ parser->Parse(mDocumentURI);
+
+ // Put the current prototype, created under PrepareToLoad, into the
+ // XUL prototype cache now. We can't do this under PrepareToLoad or
+ // overlay loading will break; search for PutPrototype in ResumeWalk
+ // and see the comment there.
+ if (fillXULCache) {
+ nsXULPrototypeCache::GetInstance()->PutPrototype(mCurrentPrototype);
+ }
+ }
+
+ NS_IF_ADDREF(*aDocListener);
+ return NS_OK;
+}
+
+// This gets invoked after a prototype for this document or one of
+// its overlays is fully built in the content sink.
+void
+XULDocument::EndLoad()
+{
+ // This can happen if an overlay fails to load
+ if (!mCurrentPrototype)
+ return;
+
+ nsresult rv;
+
+ // Whack the prototype document into the cache so that the next
+ // time somebody asks for it, they don't need to load it by hand.
+
+ nsCOMPtr<nsIURI> uri = mCurrentPrototype->GetURI();
+ bool isChrome = IsChromeURI(uri);
+
+ // Remember if the XUL cache is on
+ bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
+
+ // If the current prototype is an overlay document (non-master prototype)
+ // and we're filling the FastLoad disk cache, tell the cache we're done
+ // loading it, and write the prototype. The master prototype is put into
+ // the cache earlier in XULDocument::StartDocumentLoad.
+ if (useXULCache && mIsWritingFastLoad && isChrome &&
+ mMasterPrototype != mCurrentPrototype) {
+ nsXULPrototypeCache::GetInstance()->WritePrototype(mCurrentPrototype);
+ }
+
+ if (IsOverlayAllowed(uri)) {
+ nsCOMPtr<nsIXULOverlayProvider> reg =
+ mozilla::services::GetXULOverlayProviderService();
+
+ if (reg) {
+ nsCOMPtr<nsISimpleEnumerator> overlays;
+ rv = reg->GetStyleOverlays(uri, getter_AddRefs(overlays));
+ if (NS_FAILED(rv)) return;
+
+ bool moreSheets;
+ nsCOMPtr<nsISupports> next;
+ nsCOMPtr<nsIURI> sheetURI;
+
+ while (NS_SUCCEEDED(rv = overlays->HasMoreElements(&moreSheets)) &&
+ moreSheets) {
+ overlays->GetNext(getter_AddRefs(next));
+
+ sheetURI = do_QueryInterface(next);
+ if (!sheetURI) {
+ NS_ERROR("Chrome registry handed me a non-nsIURI object!");
+ continue;
+ }
+
+ if (IsChromeURI(sheetURI)) {
+ mCurrentPrototype->AddStyleSheetReference(sheetURI);
+ }
+ }
+ }
+
+ if (isChrome && useXULCache) {
+ // If it's a chrome prototype document, then notify any
+ // documents that raced to load the prototype, and awaited
+ // its load completion via proto->AwaitLoadDone().
+ rv = mCurrentPrototype->NotifyLoadDone();
+ if (NS_FAILED(rv)) return;
+ }
+ }
+
+ OnPrototypeLoadDone(true);
+ if (MOZ_LOG_TEST(gXULLog, LogLevel::Warning)) {
+ nsAutoCString urlspec;
+ rv = uri->GetSpec(urlspec);
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_LOG(gXULLog, LogLevel::Warning,
+ ("xul: Finished loading document '%s'", urlspec.get()));
+ }
+ }
+}
+
+NS_IMETHODIMP
+XULDocument::OnPrototypeLoadDone(bool aResumeWalk)
+{
+ nsresult rv;
+
+ // Add the style overlays from chrome registry, if any.
+ rv = AddPrototypeSheets();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = PrepareToWalk();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to prepare for walk");
+ if (NS_FAILED(rv)) return rv;
+
+ if (aResumeWalk) {
+ rv = ResumeWalk();
+ }
+ return rv;
+}
+
+// called when an error occurs parsing a document
+bool
+XULDocument::OnDocumentParserError()
+{
+ // don't report errors that are from overlays
+ if (mCurrentPrototype && mMasterPrototype != mCurrentPrototype) {
+ nsCOMPtr<nsIURI> uri = mCurrentPrototype->GetURI();
+ if (IsChromeURI(uri)) {
+ nsCOMPtr<nsIObserverService> os =
+ mozilla::services::GetObserverService();
+ if (os)
+ os->NotifyObservers(uri, "xul-overlay-parsererror",
+ EmptyString().get());
+ }
+
+ return false;
+ }
+
+ return true;
+}
+
+static void
+ClearBroadcasterMapEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry)
+{
+ BroadcasterMapEntry* entry =
+ static_cast<BroadcasterMapEntry*>(aEntry);
+ for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
+ delete entry->mListeners[i];
+ }
+ entry->mListeners.Clear();
+
+ // N.B. that we need to manually run the dtor because we
+ // constructed the nsTArray object in-place.
+ entry->mListeners.~nsTArray<BroadcastListener*>();
+}
+
+static bool
+CanBroadcast(int32_t aNameSpaceID, nsIAtom* aAttribute)
+{
+ // Don't push changes to the |id|, |ref|, |persist|, |command| or
+ // |observes| attribute.
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if ((aAttribute == nsGkAtoms::id) ||
+ (aAttribute == nsGkAtoms::ref) ||
+ (aAttribute == nsGkAtoms::persist) ||
+ (aAttribute == nsGkAtoms::command) ||
+ (aAttribute == nsGkAtoms::observes)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+struct nsAttrNameInfo
+{
+ nsAttrNameInfo(int32_t aNamespaceID, nsIAtom* aName, nsIAtom* aPrefix) :
+ mNamespaceID(aNamespaceID), mName(aName), mPrefix(aPrefix) {}
+ nsAttrNameInfo(const nsAttrNameInfo& aOther) :
+ mNamespaceID(aOther.mNamespaceID), mName(aOther.mName),
+ mPrefix(aOther.mPrefix) {}
+ int32_t mNamespaceID;
+ nsCOMPtr<nsIAtom> mName;
+ nsCOMPtr<nsIAtom> mPrefix;
+};
+
+void
+XULDocument::SynchronizeBroadcastListener(Element *aBroadcaster,
+ Element *aListener,
+ const nsAString &aAttr)
+{
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ nsDelayedBroadcastUpdate delayedUpdate(aBroadcaster, aListener,
+ aAttr);
+ mDelayedBroadcasters.AppendElement(delayedUpdate);
+ MaybeBroadcast();
+ return;
+ }
+ bool notify = mDocumentLoaded || mHandlingDelayedBroadcasters;
+
+ if (aAttr.EqualsLiteral("*")) {
+ uint32_t count = aBroadcaster->GetAttrCount();
+ nsTArray<nsAttrNameInfo> attributes(count);
+ for (uint32_t i = 0; i < count; ++i) {
+ const nsAttrName* attrName = aBroadcaster->GetAttrNameAt(i);
+ int32_t nameSpaceID = attrName->NamespaceID();
+ nsIAtom* name = attrName->LocalName();
+
+ // _Don't_ push the |id|, |ref|, or |persist| attribute's value!
+ if (! CanBroadcast(nameSpaceID, name))
+ continue;
+
+ attributes.AppendElement(nsAttrNameInfo(nameSpaceID, name,
+ attrName->GetPrefix()));
+ }
+
+ count = attributes.Length();
+ while (count-- > 0) {
+ int32_t nameSpaceID = attributes[count].mNamespaceID;
+ nsIAtom* name = attributes[count].mName;
+ nsAutoString value;
+ if (aBroadcaster->GetAttr(nameSpaceID, name, value)) {
+ aListener->SetAttr(nameSpaceID, name, attributes[count].mPrefix,
+ value, notify);
+ }
+
+#if 0
+ // XXX we don't fire the |onbroadcast| handler during
+ // initial hookup: doing so would potentially run the
+ // |onbroadcast| handler before the |onload| handler,
+ // which could define JS properties that mask XBL
+ // properties, etc.
+ ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
+#endif
+ }
+ }
+ else {
+ // Find out if the attribute is even present at all.
+ nsCOMPtr<nsIAtom> name = NS_Atomize(aAttr);
+
+ nsAutoString value;
+ if (aBroadcaster->GetAttr(kNameSpaceID_None, name, value)) {
+ aListener->SetAttr(kNameSpaceID_None, name, value, notify);
+ } else {
+ aListener->UnsetAttr(kNameSpaceID_None, name, notify);
+ }
+
+#if 0
+ // XXX we don't fire the |onbroadcast| handler during initial
+ // hookup: doing so would potentially run the |onbroadcast|
+ // handler before the |onload| handler, which could define JS
+ // properties that mask XBL properties, etc.
+ ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
+#endif
+ }
+}
+
+NS_IMETHODIMP
+XULDocument::AddBroadcastListenerFor(nsIDOMElement* aBroadcaster,
+ nsIDOMElement* aListener,
+ const nsAString& aAttr)
+{
+ ErrorResult rv;
+ nsCOMPtr<Element> broadcaster = do_QueryInterface(aBroadcaster);
+ nsCOMPtr<Element> listener = do_QueryInterface(aListener);
+ NS_ENSURE_ARG(broadcaster && listener);
+ AddBroadcastListenerFor(*broadcaster, *listener, aAttr, rv);
+ return rv.StealNSResult();
+}
+
+void
+XULDocument::AddBroadcastListenerFor(Element& aBroadcaster, Element& aListener,
+ const nsAString& aAttr, ErrorResult& aRv)
+{
+ nsresult rv =
+ nsContentUtils::CheckSameOrigin(this, &aBroadcaster);
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ rv = nsContentUtils::CheckSameOrigin(this, &aListener);
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ static const PLDHashTableOps gOps = {
+ PLDHashTable::HashVoidPtrKeyStub,
+ PLDHashTable::MatchEntryStub,
+ PLDHashTable::MoveEntryStub,
+ ClearBroadcasterMapEntry,
+ nullptr
+ };
+
+ if (! mBroadcasterMap) {
+ mBroadcasterMap = new PLDHashTable(&gOps, sizeof(BroadcasterMapEntry));
+ }
+
+ auto entry = static_cast<BroadcasterMapEntry*>
+ (mBroadcasterMap->Search(&aBroadcaster));
+ if (!entry) {
+ entry = static_cast<BroadcasterMapEntry*>
+ (mBroadcasterMap->Add(&aBroadcaster, fallible));
+
+ if (! entry) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ entry->mBroadcaster = &aBroadcaster;
+
+ // N.B. placement new to construct the nsTArray object in-place
+ new (&entry->mListeners) nsTArray<BroadcastListener*>();
+ }
+
+ // Only add the listener if it's not there already!
+ nsCOMPtr<nsIAtom> attr = NS_Atomize(aAttr);
+
+ for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
+ BroadcastListener* bl = entry->mListeners[i];
+ nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
+
+ if (blListener == &aListener && bl->mAttribute == attr)
+ return;
+ }
+
+ BroadcastListener* bl = new BroadcastListener;
+ bl->mListener = do_GetWeakReference(&aListener);
+ bl->mAttribute = attr;
+
+ entry->mListeners.AppendElement(bl);
+
+ SynchronizeBroadcastListener(&aBroadcaster, &aListener, aAttr);
+}
+
+NS_IMETHODIMP
+XULDocument::RemoveBroadcastListenerFor(nsIDOMElement* aBroadcaster,
+ nsIDOMElement* aListener,
+ const nsAString& aAttr)
+{
+ nsCOMPtr<Element> broadcaster = do_QueryInterface(aBroadcaster);
+ nsCOMPtr<Element> listener = do_QueryInterface(aListener);
+ NS_ENSURE_ARG(broadcaster && listener);
+ RemoveBroadcastListenerFor(*broadcaster, *listener, aAttr);
+ return NS_OK;
+}
+
+void
+XULDocument::RemoveBroadcastListenerFor(Element& aBroadcaster,
+ Element& aListener,
+ const nsAString& aAttr)
+{
+ // If we haven't added any broadcast listeners, then there sure
+ // aren't any to remove.
+ if (! mBroadcasterMap)
+ return;
+
+ auto entry = static_cast<BroadcasterMapEntry*>
+ (mBroadcasterMap->Search(&aBroadcaster));
+ if (entry) {
+ nsCOMPtr<nsIAtom> attr = NS_Atomize(aAttr);
+ for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
+ BroadcastListener* bl = entry->mListeners[i];
+ nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
+
+ if (blListener == &aListener && bl->mAttribute == attr) {
+ entry->mListeners.RemoveElementAt(i);
+ delete bl;
+
+ if (entry->mListeners.IsEmpty())
+ mBroadcasterMap->RemoveEntry(entry);
+
+ break;
+ }
+ }
+ }
+}
+
+nsresult
+XULDocument::ExecuteOnBroadcastHandlerFor(Element* aBroadcaster,
+ Element* aListener,
+ nsIAtom* aAttr)
+{
+ // Now we execute the onchange handler in the context of the
+ // observer. We need to find the observer in order to
+ // execute the handler.
+
+ for (nsIContent* child = aListener->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+
+ // Look for an <observes> element beneath the listener. This
+ // ought to have an |element| attribute that refers to
+ // aBroadcaster, and an |attribute| element that tells us what
+ // attriubtes we're listening for.
+ if (!child->NodeInfo()->Equals(nsGkAtoms::observes, kNameSpaceID_XUL))
+ continue;
+
+ // Is this the element that was listening to us?
+ nsAutoString listeningToID;
+ child->GetAttr(kNameSpaceID_None, nsGkAtoms::element, listeningToID);
+
+ nsAutoString broadcasterID;
+ aBroadcaster->GetAttr(kNameSpaceID_None, nsGkAtoms::id, broadcasterID);
+
+ if (listeningToID != broadcasterID)
+ continue;
+
+ // We are observing the broadcaster, but is this the right
+ // attribute?
+ nsAutoString listeningToAttribute;
+ child->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute,
+ listeningToAttribute);
+
+ if (!aAttr->Equals(listeningToAttribute) &&
+ !listeningToAttribute.EqualsLiteral("*")) {
+ continue;
+ }
+
+ // This is the right <observes> element. Execute the
+ // |onbroadcast| event handler
+ WidgetEvent event(true, eXULBroadcast);
+
+ nsCOMPtr<nsIPresShell> shell = GetShell();
+ if (shell) {
+ RefPtr<nsPresContext> aPresContext = shell->GetPresContext();
+
+ // Handle the DOM event
+ nsEventStatus status = nsEventStatus_eIgnore;
+ EventDispatcher::Dispatch(child, aPresContext, &event, nullptr,
+ &status);
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+XULDocument::AttributeWillChange(nsIDocument* aDocument,
+ Element* aElement, int32_t aNameSpaceID,
+ nsIAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aNewValue)
+{
+ MOZ_ASSERT(aElement, "Null content!");
+ NS_PRECONDITION(aAttribute, "Must have an attribute that's changing!");
+
+ // XXXbz check aNameSpaceID, dammit!
+ // See if we need to update our ref map.
+ if (aAttribute == nsGkAtoms::ref) {
+ // Might not need this, but be safe for now.
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ RemoveElementFromRefMap(aElement);
+ }
+}
+
+static bool
+ShouldPersistAttribute(Element* aElement, nsIAtom* aAttribute)
+{
+ if (aElement->IsXULElement(nsGkAtoms::window)) {
+ // This is not an element of the top document, its owner is
+ // not an nsXULWindow. Persist it.
+ if (aElement->OwnerDoc()->GetParentDocument()) {
+ return true;
+ }
+ // The following attributes of xul:window should be handled in
+ // nsXULWindow::SavePersistentAttributes instead of here.
+ if (aAttribute == nsGkAtoms::screenX ||
+ aAttribute == nsGkAtoms::screenY ||
+ aAttribute == nsGkAtoms::width ||
+ aAttribute == nsGkAtoms::height ||
+ aAttribute == nsGkAtoms::sizemode) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void
+XULDocument::AttributeChanged(nsIDocument* aDocument,
+ Element* aElement, int32_t aNameSpaceID,
+ nsIAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ NS_ASSERTION(aDocument == this, "unexpected doc");
+
+ // Might not need this, but be safe for now.
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+
+ // XXXbz check aNameSpaceID, dammit!
+ // See if we need to update our ref map.
+ if (aAttribute == nsGkAtoms::ref) {
+ AddElementToRefMap(aElement);
+ }
+
+ // Synchronize broadcast listeners
+ if (mBroadcasterMap &&
+ CanBroadcast(aNameSpaceID, aAttribute)) {
+ auto entry = static_cast<BroadcasterMapEntry*>
+ (mBroadcasterMap->Search(aElement));
+
+ if (entry) {
+ // We've got listeners: push the value.
+ nsAutoString value;
+ bool attrSet = aElement->GetAttr(kNameSpaceID_None, aAttribute, value);
+
+ for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
+ BroadcastListener* bl = entry->mListeners[i];
+ if ((bl->mAttribute == aAttribute) ||
+ (bl->mAttribute == nsGkAtoms::_asterisk)) {
+ nsCOMPtr<Element> listenerEl
+ = do_QueryReferent(bl->mListener);
+ if (listenerEl) {
+ nsAutoString currentValue;
+ bool hasAttr = listenerEl->GetAttr(kNameSpaceID_None,
+ aAttribute,
+ currentValue);
+ // We need to update listener only if we're
+ // (1) removing an existing attribute,
+ // (2) adding a new attribute or
+ // (3) changing the value of an attribute.
+ bool needsAttrChange =
+ attrSet != hasAttr || !value.Equals(currentValue);
+ nsDelayedBroadcastUpdate delayedUpdate(aElement,
+ listenerEl,
+ aAttribute,
+ value,
+ attrSet,
+ needsAttrChange);
+
+ size_t index =
+ mDelayedAttrChangeBroadcasts.IndexOf(delayedUpdate,
+ 0, nsDelayedBroadcastUpdate::Comparator());
+ if (index != mDelayedAttrChangeBroadcasts.NoIndex) {
+ if (mHandlingDelayedAttrChange) {
+ NS_WARNING("Broadcasting loop!");
+ continue;
+ }
+ mDelayedAttrChangeBroadcasts.RemoveElementAt(index);
+ }
+
+ mDelayedAttrChangeBroadcasts.AppendElement(delayedUpdate);
+ }
+ }
+ }
+ }
+ }
+
+ // checks for modifications in broadcasters
+ bool listener, resolved;
+ CheckBroadcasterHookup(aElement, &listener, &resolved);
+
+ // See if there is anything we need to persist in the localstore.
+ //
+ // XXX Namespace handling broken :-(
+ nsAutoString persist;
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist);
+ // Persistence of attributes of xul:window is handled in nsXULWindow.
+ if (ShouldPersistAttribute(aElement, aAttribute) && !persist.IsEmpty() &&
+ // XXXldb This should check that it's a token, not just a substring.
+ persist.Find(nsDependentAtomString(aAttribute)) >= 0) {
+ nsContentUtils::AddScriptRunner(NewRunnableMethod
+ <nsIContent*, int32_t, nsIAtom*>
+ (this, &XULDocument::DoPersist, aElement, kNameSpaceID_None,
+ aAttribute));
+ }
+}
+
+void
+XULDocument::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer)
+{
+ NS_ASSERTION(aDocument == this, "unexpected doc");
+
+ // Might not need this, but be safe for now.
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+
+ // Update our element map
+ nsresult rv = NS_OK;
+ for (nsIContent* cur = aFirstNewContent; cur && NS_SUCCEEDED(rv);
+ cur = cur->GetNextSibling()) {
+ rv = AddSubtreeToDocument(cur);
+ }
+}
+
+void
+XULDocument::ContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ NS_ASSERTION(aDocument == this, "unexpected doc");
+
+ // Might not need this, but be safe for now.
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+
+ AddSubtreeToDocument(aChild);
+}
+
+void
+XULDocument::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ NS_ASSERTION(aDocument == this, "unexpected doc");
+
+ // Might not need this, but be safe for now.
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+
+ RemoveSubtreeFromDocument(aChild);
+}
+
+//----------------------------------------------------------------------
+//
+// nsIXULDocument interface
+//
+
+void
+XULDocument::GetElementsForID(const nsAString& aID,
+ nsCOMArray<nsIContent>& aElements)
+{
+ aElements.Clear();
+
+ nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aID);
+ if (entry) {
+ entry->AppendAllIdContent(&aElements);
+ }
+ nsRefMapEntry *refEntry = mRefMap.GetEntry(aID);
+ if (refEntry) {
+ refEntry->AppendAll(&aElements);
+ }
+}
+
+nsresult
+XULDocument::AddForwardReference(nsForwardReference* aRef)
+{
+ if (mResolutionPhase < aRef->GetPhase()) {
+ if (!mForwardReferences.AppendElement(aRef)) {
+ delete aRef;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ else {
+ NS_ERROR("forward references have already been resolved");
+ delete aRef;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+XULDocument::ResolveForwardReferences()
+{
+ if (mResolutionPhase == nsForwardReference::eDone)
+ return NS_OK;
+
+ NS_ASSERTION(mResolutionPhase == nsForwardReference::eStart,
+ "nested ResolveForwardReferences()");
+
+ // Resolve each outstanding 'forward' reference. We iterate
+ // through the list of forward references until no more forward
+ // references can be resolved. This annealing process is
+ // guaranteed to converge because we've "closed the gate" to new
+ // forward references.
+
+ const nsForwardReference::Phase* pass = nsForwardReference::kPasses;
+ while ((mResolutionPhase = *pass) != nsForwardReference::eDone) {
+ uint32_t previous = 0;
+ while (mForwardReferences.Length() &&
+ mForwardReferences.Length() != previous) {
+ previous = mForwardReferences.Length();
+
+ for (uint32_t i = 0; i < mForwardReferences.Length(); ++i) {
+ nsForwardReference* fwdref = mForwardReferences[i];
+
+ if (fwdref->GetPhase() == *pass) {
+ nsForwardReference::Result result = fwdref->Resolve();
+
+ switch (result) {
+ case nsForwardReference::eResolve_Succeeded:
+ case nsForwardReference::eResolve_Error:
+ mForwardReferences.RemoveElementAt(i);
+
+ // fixup because we removed from list
+ --i;
+ break;
+
+ case nsForwardReference::eResolve_Later:
+ // do nothing. we'll try again later
+ ;
+ }
+
+ if (mResolutionPhase == nsForwardReference::eStart) {
+ // Resolve() loaded a dynamic overlay,
+ // (see XULDocument::LoadOverlayInternal()).
+ // Return for now, we will be called again.
+ return NS_OK;
+ }
+ }
+ }
+ }
+
+ ++pass;
+ }
+
+ mForwardReferences.Clear();
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+//
+// nsIDOMDocument interface
+//
+
+NS_IMETHODIMP
+XULDocument::GetElementsByAttribute(const nsAString& aAttribute,
+ const nsAString& aValue,
+ nsIDOMNodeList** aReturn)
+{
+ *aReturn = GetElementsByAttribute(aAttribute, aValue).take();
+ return NS_OK;
+}
+
+already_AddRefed<nsINodeList>
+XULDocument::GetElementsByAttribute(const nsAString& aAttribute,
+ const nsAString& aValue)
+{
+ nsCOMPtr<nsIAtom> attrAtom(NS_Atomize(aAttribute));
+ void* attrValue = new nsString(aValue);
+ RefPtr<nsContentList> list = new nsContentList(this,
+ MatchAttribute,
+ nsContentUtils::DestroyMatchString,
+ attrValue,
+ true,
+ attrAtom,
+ kNameSpaceID_Unknown);
+
+ return list.forget();
+}
+
+NS_IMETHODIMP
+XULDocument::GetElementsByAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aAttribute,
+ const nsAString& aValue,
+ nsIDOMNodeList** aReturn)
+{
+ ErrorResult rv;
+ *aReturn = GetElementsByAttributeNS(aNamespaceURI, aAttribute,
+ aValue, rv).take();
+ return rv.StealNSResult();
+}
+
+already_AddRefed<nsINodeList>
+XULDocument::GetElementsByAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aAttribute,
+ const nsAString& aValue,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsIAtom> attrAtom(NS_Atomize(aAttribute));
+ void* attrValue = new nsString(aValue);
+
+ int32_t nameSpaceId = kNameSpaceID_Wildcard;
+ if (!aNamespaceURI.EqualsLiteral("*")) {
+ nsresult rv =
+ nsContentUtils::NameSpaceManager()->RegisterNameSpace(aNamespaceURI,
+ nameSpaceId);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+ }
+
+ RefPtr<nsContentList> list = new nsContentList(this,
+ MatchAttribute,
+ nsContentUtils::DestroyMatchString,
+ attrValue,
+ true,
+ attrAtom,
+ nameSpaceId);
+ return list.forget();
+}
+
+NS_IMETHODIMP
+XULDocument::Persist(const nsAString& aID,
+ const nsAString& aAttr)
+{
+ // If we're currently reading persisted attributes out of the
+ // localstore, _don't_ re-enter and try to set them again!
+ if (mApplyingPersistedAttrs)
+ return NS_OK;
+
+ Element* element = nsDocument::GetElementById(aID);
+ if (!element)
+ return NS_OK;
+
+ nsCOMPtr<nsIAtom> tag;
+ int32_t nameSpaceID;
+
+ RefPtr<mozilla::dom::NodeInfo> ni = element->GetExistingAttrNameFromQName(aAttr);
+ nsresult rv;
+ if (ni) {
+ tag = ni->NameAtom();
+ nameSpaceID = ni->NamespaceID();
+ }
+ else {
+ // Make sure that this QName is going to be valid.
+ const char16_t *colon;
+ rv = nsContentUtils::CheckQName(PromiseFlatString(aAttr), true, &colon);
+
+ if (NS_FAILED(rv)) {
+ // There was an invalid character or it was malformed.
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (colon) {
+ // We don't really handle namespace qualifiers in attribute names.
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ tag = NS_Atomize(aAttr);
+ NS_ENSURE_TRUE(tag, NS_ERROR_OUT_OF_MEMORY);
+
+ nameSpaceID = kNameSpaceID_None;
+ }
+
+ return Persist(element, nameSpaceID, tag);
+}
+
+nsresult
+XULDocument::Persist(nsIContent* aElement, int32_t aNameSpaceID,
+ nsIAtom* aAttribute)
+{
+ // For non-chrome documents, persistance is simply broken
+ if (!nsContentUtils::IsSystemPrincipal(NodePrincipal()))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (!mLocalStore) {
+ mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
+ if (NS_WARN_IF(!mLocalStore)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ }
+
+ nsAutoString id;
+
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
+ nsAtomString attrstr(aAttribute);
+
+ nsAutoString valuestr;
+ aElement->GetAttr(kNameSpaceID_None, aAttribute, valuestr);
+
+ nsAutoCString utf8uri;
+ nsresult rv = mDocumentURI->GetSpec(utf8uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ NS_ConvertUTF8toUTF16 uri(utf8uri);
+
+ bool hasAttr;
+ rv = mLocalStore->HasValue(uri, id, attrstr, &hasAttr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (hasAttr && valuestr.IsEmpty()) {
+ return mLocalStore->RemoveValue(uri, id, attrstr);
+ } else {
+ return mLocalStore->SetValue(uri, id, attrstr, valuestr);
+ }
+}
+
+
+nsresult
+XULDocument::GetViewportSize(int32_t* aWidth,
+ int32_t* aHeight)
+{
+ *aWidth = *aHeight = 0;
+
+ FlushPendingNotifications(Flush_Layout);
+
+ nsIPresShell *shell = GetShell();
+ NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
+
+ nsIFrame* frame = shell->GetRootFrame();
+ NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
+
+ nsSize size = frame->GetSize();
+
+ *aWidth = nsPresContext::AppUnitsToIntCSSPixels(size.width);
+ *aHeight = nsPresContext::AppUnitsToIntCSSPixels(size.height);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULDocument::GetWidth(int32_t* aWidth)
+{
+ NS_ENSURE_ARG_POINTER(aWidth);
+
+ int32_t height;
+ return GetViewportSize(aWidth, &height);
+}
+
+int32_t
+XULDocument::GetWidth(ErrorResult& aRv)
+{
+ int32_t width;
+ aRv = GetWidth(&width);
+ return width;
+}
+
+NS_IMETHODIMP
+XULDocument::GetHeight(int32_t* aHeight)
+{
+ NS_ENSURE_ARG_POINTER(aHeight);
+
+ int32_t width;
+ return GetViewportSize(&width, aHeight);
+}
+
+int32_t
+XULDocument::GetHeight(ErrorResult& aRv)
+{
+ int32_t height;
+ aRv = GetHeight(&height);
+ return height;
+}
+
+JSObject*
+GetScopeObjectOfNode(nsIDOMNode* node)
+{
+ MOZ_ASSERT(node, "Must not be called with null.");
+
+ // Window root occasionally keeps alive a node of a document whose
+ // window is already dead. If in this brief period someone calls
+ // GetPopupNode and we return that node, nsNodeSH::PreCreate will throw,
+ // because it will not know which scope this node belongs to. Returning
+ // an orphan node like that to JS would be a bug anyway, so to avoid
+ // this, let's do the same check as nsNodeSH::PreCreate does to
+ // determine the scope and if it fails let's just return null in
+ // XULDocument::GetPopupNode.
+ nsCOMPtr<nsINode> inode = do_QueryInterface(node);
+ MOZ_ASSERT(inode, "How can this happen?");
+
+ nsIDocument* doc = inode->OwnerDoc();
+ MOZ_ASSERT(inode, "This should never happen.");
+
+ nsIGlobalObject* global = doc->GetScopeObject();
+ return global ? global->GetGlobalJSObject() : nullptr;
+}
+
+//----------------------------------------------------------------------
+//
+// nsIDOMXULDocument interface
+//
+
+NS_IMETHODIMP
+XULDocument::GetPopupNode(nsIDOMNode** aNode)
+{
+ *aNode = nullptr;
+
+ nsCOMPtr<nsIDOMNode> node;
+ nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
+ if (rootWin)
+ node = rootWin->GetPopupNode(); // addref happens here
+
+ if (!node) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ node = pm->GetLastTriggerPopupNode(this);
+ }
+ }
+
+ if (node && nsContentUtils::CanCallerAccess(node)
+ && GetScopeObjectOfNode(node)) {
+ node.forget(aNode);
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsINode>
+XULDocument::GetPopupNode()
+{
+ nsCOMPtr<nsIDOMNode> node;
+ DebugOnly<nsresult> rv = GetPopupNode(getter_AddRefs(node));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ nsCOMPtr<nsINode> retval(do_QueryInterface(node));
+ return retval.forget();
+}
+
+NS_IMETHODIMP
+XULDocument::SetPopupNode(nsIDOMNode* aNode)
+{
+ if (aNode) {
+ // only allow real node objects
+ nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
+ NS_ENSURE_ARG(node);
+ }
+
+ nsCOMPtr<nsPIWindowRoot> rootWin = GetWindowRoot();
+ if (rootWin)
+ rootWin->SetPopupNode(aNode); // addref happens here
+
+ return NS_OK;
+}
+
+void
+XULDocument::SetPopupNode(nsINode* aNode)
+{
+ nsCOMPtr<nsIDOMNode> node(do_QueryInterface(aNode));
+ DebugOnly<nsresult> rv = SetPopupNode(node);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+// Returns the rangeOffset element from the XUL Popup Manager. This is for
+// chrome callers only.
+NS_IMETHODIMP
+XULDocument::GetPopupRangeParent(nsIDOMNode** aRangeParent)
+{
+ NS_ENSURE_ARG_POINTER(aRangeParent);
+ *aRangeParent = nullptr;
+
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (!pm)
+ return NS_ERROR_FAILURE;
+
+ int32_t offset;
+ pm->GetMouseLocation(aRangeParent, &offset);
+
+ if (*aRangeParent && !nsContentUtils::CanCallerAccess(*aRangeParent)) {
+ NS_RELEASE(*aRangeParent);
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsINode>
+XULDocument::GetPopupRangeParent(ErrorResult& aRv)
+{
+ nsCOMPtr<nsIDOMNode> node;
+ aRv = GetPopupRangeParent(getter_AddRefs(node));
+ nsCOMPtr<nsINode> retval(do_QueryInterface(node));
+ return retval.forget();
+}
+
+
+// Returns the rangeOffset element from the XUL Popup Manager. We check the
+// rangeParent to determine if the caller has rights to access to the data.
+NS_IMETHODIMP
+XULDocument::GetPopupRangeOffset(int32_t* aRangeOffset)
+{
+ ErrorResult rv;
+ *aRangeOffset = GetPopupRangeOffset(rv);
+ return rv.StealNSResult();
+}
+
+int32_t
+XULDocument::GetPopupRangeOffset(ErrorResult& aRv)
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (!pm) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return 0;
+ }
+
+ int32_t offset;
+ nsCOMPtr<nsIDOMNode> parent;
+ pm->GetMouseLocation(getter_AddRefs(parent), &offset);
+
+ if (parent && !nsContentUtils::CanCallerAccess(parent)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return 0;
+ }
+ return offset;
+}
+
+NS_IMETHODIMP
+XULDocument::GetTooltipNode(nsIDOMNode** aNode)
+{
+ *aNode = nullptr;
+
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ nsCOMPtr<nsIDOMNode> node = pm->GetLastTriggerTooltipNode(this);
+ if (node && nsContentUtils::CanCallerAccess(node))
+ node.forget(aNode);
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsINode>
+XULDocument::GetTooltipNode()
+{
+ nsCOMPtr<nsIDOMNode> node;
+ DebugOnly<nsresult> rv = GetTooltipNode(getter_AddRefs(node));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ nsCOMPtr<nsINode> retval(do_QueryInterface(node));
+ return retval.forget();
+}
+
+NS_IMETHODIMP
+XULDocument::SetTooltipNode(nsIDOMNode* aNode)
+{
+ // do nothing
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+XULDocument::GetCommandDispatcher(nsIDOMXULCommandDispatcher** aTracker)
+{
+ *aTracker = mCommandDispatcher;
+ NS_IF_ADDREF(*aTracker);
+ return NS_OK;
+}
+
+Element*
+XULDocument::GetElementById(const nsAString& aId)
+{
+ if (!CheckGetElementByIdArg(aId))
+ return nullptr;
+
+ nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aId);
+ if (entry) {
+ Element* element = entry->GetIdElement();
+ if (element)
+ return element;
+ }
+
+ nsRefMapEntry* refEntry = mRefMap.GetEntry(aId);
+ if (refEntry) {
+ NS_ASSERTION(refEntry->GetFirstElement(),
+ "nsRefMapEntries should have nonempty content lists");
+ return refEntry->GetFirstElement();
+ }
+ return nullptr;
+}
+
+nsresult
+XULDocument::AddElementToDocumentPre(Element* aElement)
+{
+ // Do a bunch of work that's necessary when an element gets added
+ // to the XUL Document.
+ nsresult rv;
+
+ // 1. Add the element to the resource-to-element map. Also add it to
+ // the id map, since it seems this can be called when creating
+ // elements from prototypes.
+ nsIAtom* id = aElement->GetID();
+ if (id) {
+ // FIXME: Shouldn't BindToTree take care of this?
+ nsAutoScriptBlocker scriptBlocker;
+ AddToIdTable(aElement, id);
+ }
+ rv = AddElementToRefMap(aElement);
+ if (NS_FAILED(rv)) return rv;
+
+ // 2. If the element is a 'command updater' (i.e., has a
+ // "commandupdater='true'" attribute), then add the element to the
+ // document's command dispatcher
+ if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::commandupdater,
+ nsGkAtoms::_true, eCaseMatters)) {
+ rv = nsXULContentUtils::SetCommandUpdater(this, aElement);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // 3. Check for a broadcaster hookup attribute, in which case
+ // we'll hook the node up as a listener on a broadcaster.
+ bool listener, resolved;
+ rv = CheckBroadcasterHookup(aElement, &listener, &resolved);
+ if (NS_FAILED(rv)) return rv;
+
+ // If it's not there yet, we may be able to defer hookup until
+ // later.
+ if (listener && !resolved && (mResolutionPhase != nsForwardReference::eDone)) {
+ BroadcasterHookup* hookup = new BroadcasterHookup(this, aElement);
+ rv = AddForwardReference(hookup);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+XULDocument::AddElementToDocumentPost(Element* aElement)
+{
+ // We need to pay special attention to the keyset tag to set up a listener
+ if (aElement->NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
+ // Create our XUL key listener and hook it up.
+ nsXBLService::AttachGlobalKeyHandler(aElement);
+ }
+
+ // See if we need to attach a XUL template to this node
+ bool needsHookup;
+ nsresult rv = CheckTemplateBuilderHookup(aElement, &needsHookup);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (needsHookup) {
+ if (mResolutionPhase == nsForwardReference::eDone) {
+ rv = CreateTemplateBuilder(aElement);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ else {
+ TemplateBuilderHookup* hookup = new TemplateBuilderHookup(aElement);
+ rv = AddForwardReference(hookup);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULDocument::AddSubtreeToDocument(nsIContent* aContent)
+{
+ NS_ASSERTION(aContent->GetUncomposedDoc() == this, "Element not in doc!");
+ // From here on we only care about elements.
+ if (!aContent->IsElement()) {
+ return NS_OK;
+ }
+
+ Element* aElement = aContent->AsElement();
+
+ // Do pre-order addition magic
+ nsresult rv = AddElementToDocumentPre(aElement);
+ if (NS_FAILED(rv)) return rv;
+
+ // Recurse to children
+ for (nsIContent* child = aElement->GetLastChild();
+ child;
+ child = child->GetPreviousSibling()) {
+
+ rv = AddSubtreeToDocument(child);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ // Do post-order addition magic
+ return AddElementToDocumentPost(aElement);
+}
+
+NS_IMETHODIMP
+XULDocument::RemoveSubtreeFromDocument(nsIContent* aContent)
+{
+ // From here on we only care about elements.
+ if (!aContent->IsElement()) {
+ return NS_OK;
+ }
+
+ Element* aElement = aContent->AsElement();
+
+ // Do a bunch of cleanup to remove an element from the XUL
+ // document.
+ nsresult rv;
+
+ if (aElement->NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
+ nsXBLService::DetachGlobalKeyHandler(aElement);
+ }
+
+ // 1. Remove any children from the document.
+ for (nsIContent* child = aElement->GetLastChild();
+ child;
+ child = child->GetPreviousSibling()) {
+
+ rv = RemoveSubtreeFromDocument(child);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ // 2. Remove the element from the resource-to-element map.
+ // Also remove it from the id map, since we added it in
+ // AddElementToDocumentPre().
+ RemoveElementFromRefMap(aElement);
+ nsIAtom* id = aElement->GetID();
+ if (id) {
+ // FIXME: Shouldn't UnbindFromTree take care of this?
+ nsAutoScriptBlocker scriptBlocker;
+ RemoveFromIdTable(aElement, id);
+ }
+
+ // 3. If the element is a 'command updater', then remove the
+ // element from the document's command dispatcher.
+ if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::commandupdater,
+ nsGkAtoms::_true, eCaseMatters)) {
+ nsCOMPtr<nsIDOMElement> domelement = do_QueryInterface(aElement);
+ NS_ASSERTION(domelement != nullptr, "not a DOM element");
+ if (! domelement)
+ return NS_ERROR_UNEXPECTED;
+
+ rv = mCommandDispatcher->RemoveCommandUpdater(domelement);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // 4. Remove the element from our broadcaster map, since it is no longer
+ // in the document.
+ nsCOMPtr<Element> broadcaster, listener;
+ nsAutoString attribute, broadcasterID;
+ rv = FindBroadcaster(aElement, getter_AddRefs(listener),
+ broadcasterID, attribute, getter_AddRefs(broadcaster));
+ if (rv == NS_FINDBROADCASTER_FOUND) {
+ RemoveBroadcastListenerFor(*broadcaster, *listener, attribute);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULDocument::SetTemplateBuilderFor(nsIContent* aContent,
+ nsIXULTemplateBuilder* aBuilder)
+{
+ if (! mTemplateBuilderTable) {
+ if (!aBuilder) {
+ return NS_OK;
+ }
+ mTemplateBuilderTable = new BuilderTable;
+ }
+
+ if (aBuilder) {
+ mTemplateBuilderTable->Put(aContent, aBuilder);
+ }
+ else {
+ mTemplateBuilderTable->Remove(aContent);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULDocument::GetTemplateBuilderFor(nsIContent* aContent,
+ nsIXULTemplateBuilder** aResult)
+{
+ if (mTemplateBuilderTable) {
+ mTemplateBuilderTable->Get(aContent, aResult);
+ }
+ else
+ *aResult = nullptr;
+
+ return NS_OK;
+}
+
+static void
+GetRefMapAttribute(Element* aElement, nsAutoString* aValue)
+{
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::ref, *aValue);
+}
+
+nsresult
+XULDocument::AddElementToRefMap(Element* aElement)
+{
+ // Look at the element's 'ref' attribute, and if set,
+ // add an entry in the resource-to-element map to the element.
+ nsAutoString value;
+ GetRefMapAttribute(aElement, &value);
+ if (!value.IsEmpty()) {
+ nsRefMapEntry *entry = mRefMap.PutEntry(value);
+ if (!entry)
+ return NS_ERROR_OUT_OF_MEMORY;
+ if (!entry->AddElement(aElement))
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+void
+XULDocument::RemoveElementFromRefMap(Element* aElement)
+{
+ // Remove the element from the resource-to-element map.
+ nsAutoString value;
+ GetRefMapAttribute(aElement, &value);
+ if (!value.IsEmpty()) {
+ nsRefMapEntry *entry = mRefMap.GetEntry(value);
+ if (!entry)
+ return;
+ if (entry->RemoveElement(aElement)) {
+ mRefMap.RemoveEntry(entry);
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+//
+// nsIDOMNode interface
+//
+
+nsresult
+XULDocument::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const
+{
+ // We don't allow cloning of a XUL document
+ *aResult = nullptr;
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+}
+
+
+//----------------------------------------------------------------------
+//
+// Implementation methods
+//
+
+nsresult
+XULDocument::Init()
+{
+ nsresult rv = XMLDocument::Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create our command dispatcher and hook it up.
+ mCommandDispatcher = new nsXULCommandDispatcher(this);
+
+ if (gRefCnt++ == 0) {
+ // ensure that the XUL prototype cache is instantiated successfully,
+ // so that we can use nsXULPrototypeCache::GetInstance() without
+ // null-checks in the rest of the class.
+ nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
+ if (!cache) {
+ NS_ERROR("Could not instantiate nsXULPrototypeCache");
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ Preferences::RegisterCallback(XULDocument::DirectionChanged,
+ "intl.uidirection.", this);
+
+ return NS_OK;
+}
+
+
+nsresult
+XULDocument::StartLayout(void)
+{
+ mMayStartLayout = true;
+ nsCOMPtr<nsIPresShell> shell = GetShell();
+ if (shell) {
+ // Resize-reflow this time
+ nsPresContext *cx = shell->GetPresContext();
+ NS_ASSERTION(cx != nullptr, "no pres context");
+ if (! cx)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIDocShell> docShell = cx->GetDocShell();
+ NS_ASSERTION(docShell != nullptr, "container is not a docshell");
+ if (! docShell)
+ return NS_ERROR_UNEXPECTED;
+
+ nsresult rv = NS_OK;
+ nsRect r = cx->GetVisibleArea();
+ rv = shell->Initialize(r.width, r.height);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+/* static */
+bool
+XULDocument::MatchAttribute(nsIContent* aContent,
+ int32_t aNamespaceID,
+ nsIAtom* aAttrName,
+ void* aData)
+{
+ NS_PRECONDITION(aContent, "Must have content node to work with!");
+ nsString* attrValue = static_cast<nsString*>(aData);
+ if (aNamespaceID != kNameSpaceID_Unknown &&
+ aNamespaceID != kNameSpaceID_Wildcard) {
+ return attrValue->EqualsLiteral("*") ?
+ aContent->HasAttr(aNamespaceID, aAttrName) :
+ aContent->AttrValueIs(aNamespaceID, aAttrName, *attrValue,
+ eCaseMatters);
+ }
+
+ // Qualified name match. This takes more work.
+
+ uint32_t count = aContent->GetAttrCount();
+ for (uint32_t i = 0; i < count; ++i) {
+ const nsAttrName* name = aContent->GetAttrNameAt(i);
+ bool nameMatch;
+ if (name->IsAtom()) {
+ nameMatch = name->Atom() == aAttrName;
+ } else if (aNamespaceID == kNameSpaceID_Wildcard) {
+ nameMatch = name->NodeInfo()->Equals(aAttrName);
+ } else {
+ nameMatch = name->NodeInfo()->QualifiedNameEquals(aAttrName);
+ }
+
+ if (nameMatch) {
+ return attrValue->EqualsLiteral("*") ||
+ aContent->AttrValueIs(name->NamespaceID(), name->LocalName(),
+ *attrValue, eCaseMatters);
+ }
+ }
+
+ return false;
+}
+
+nsresult
+XULDocument::PrepareToLoad(nsISupports* aContainer,
+ const char* aCommand,
+ nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ nsIParser** aResult)
+{
+ // Get the document's principal
+ nsCOMPtr<nsIPrincipal> principal;
+ nsContentUtils::GetSecurityManager()->
+ GetChannelResultPrincipal(aChannel, getter_AddRefs(principal));
+ return PrepareToLoadPrototype(mDocumentURI, aCommand, principal, aResult);
+}
+
+
+nsresult
+XULDocument::PrepareToLoadPrototype(nsIURI* aURI, const char* aCommand,
+ nsIPrincipal* aDocumentPrincipal,
+ nsIParser** aResult)
+{
+ nsresult rv;
+
+ // Create a new prototype document.
+ rv = NS_NewXULPrototypeDocument(getter_AddRefs(mCurrentPrototype));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mCurrentPrototype->InitPrincipal(aURI, aDocumentPrincipal);
+ if (NS_FAILED(rv)) {
+ mCurrentPrototype = nullptr;
+ return rv;
+ }
+
+ // Bootstrap the master document prototype.
+ if (! mMasterPrototype) {
+ mMasterPrototype = mCurrentPrototype;
+ // Set our principal based on the master proto.
+ SetPrincipal(aDocumentPrincipal);
+ }
+
+ // Create a XUL content sink, a parser, and kick off a load for
+ // the overlay.
+ RefPtr<XULContentSinkImpl> sink = new XULContentSinkImpl();
+
+ rv = sink->Init(this, mCurrentPrototype);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to initialize datasource sink");
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIParser> parser = do_CreateInstance(kParserCID, &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create parser");
+ if (NS_FAILED(rv)) return rv;
+
+ parser->SetCommand(nsCRT::strcmp(aCommand, "view-source") ? eViewNormal :
+ eViewSource);
+
+ parser->SetDocumentCharset(NS_LITERAL_CSTRING("UTF-8"),
+ kCharsetFromDocTypeDefault);
+ parser->SetContentSink(sink); // grabs a reference to the parser
+
+ parser.forget(aResult);
+ return NS_OK;
+}
+
+
+nsresult
+XULDocument::ApplyPersistentAttributes()
+{
+ // For non-chrome documents, persistance is simply broken
+ if (!nsContentUtils::IsSystemPrincipal(NodePrincipal()))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // Add all of the 'persisted' attributes into the content
+ // model.
+ if (!mLocalStore) {
+ mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1");
+ if (NS_WARN_IF(!mLocalStore)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ }
+
+ mApplyingPersistedAttrs = true;
+ ApplyPersistentAttributesInternal();
+ mApplyingPersistedAttrs = false;
+
+ // After we've applied persistence once, we should only reapply
+ // it to nodes created by overlays
+ mRestrictPersistence = true;
+ mPersistenceIds.Clear();
+
+ return NS_OK;
+}
+
+
+nsresult
+XULDocument::ApplyPersistentAttributesInternal()
+{
+ nsCOMArray<nsIContent> elements;
+
+ nsAutoCString utf8uri;
+ nsresult rv = mDocumentURI->GetSpec(utf8uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ NS_ConvertUTF8toUTF16 uri(utf8uri);
+
+ // Get a list of element IDs for which persisted values are available
+ nsCOMPtr<nsIStringEnumerator> ids;
+ rv = mLocalStore->GetIDsEnumerator(uri, getter_AddRefs(ids));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ while (1) {
+ bool hasmore = false;
+ ids->HasMore(&hasmore);
+ if (!hasmore) {
+ break;
+ }
+
+ nsAutoString id;
+ ids->GetNext(id);
+
+ if (mRestrictPersistence && !mPersistenceIds.Contains(id)) {
+ continue;
+ }
+
+ // This will clear the array if there are no elements.
+ GetElementsForID(id, elements);
+ if (!elements.Count()) {
+ continue;
+ }
+
+ rv = ApplyPersistentAttributesToElements(id, elements);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+XULDocument::ApplyPersistentAttributesToElements(const nsAString &aID,
+ nsCOMArray<nsIContent>& aElements)
+{
+ nsAutoCString utf8uri;
+ nsresult rv = mDocumentURI->GetSpec(utf8uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ NS_ConvertUTF8toUTF16 uri(utf8uri);
+
+ // Get a list of attributes for which persisted values are available
+ nsCOMPtr<nsIStringEnumerator> attrs;
+ rv = mLocalStore->GetAttributeEnumerator(uri, aID, getter_AddRefs(attrs));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ while (1) {
+ bool hasmore = PR_FALSE;
+ attrs->HasMore(&hasmore);
+ if (!hasmore) {
+ break;
+ }
+
+ nsAutoString attrstr;
+ attrs->GetNext(attrstr);
+
+ nsAutoString value;
+ rv = mLocalStore->GetValue(uri, aID, attrstr, value);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIAtom> attr = NS_Atomize(attrstr);
+ if (NS_WARN_IF(!attr)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint32_t cnt = aElements.Count();
+
+ for (int32_t i = int32_t(cnt) - 1; i >= 0; --i) {
+ nsCOMPtr<nsIContent> element = aElements.SafeObjectAt(i);
+ if (!element) {
+ continue;
+ }
+
+ rv = element->SetAttr(kNameSpaceID_None, attr, value, PR_TRUE);
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+XULDocument::TraceProtos(JSTracer* aTrc, uint32_t aGCNumber)
+{
+ uint32_t i, count = mPrototypes.Length();
+ for (i = 0; i < count; ++i) {
+ mPrototypes[i]->TraceProtos(aTrc, aGCNumber);
+ }
+
+ if (mCurrentPrototype) {
+ mCurrentPrototype->TraceProtos(aTrc, aGCNumber);
+ }
+}
+
+//----------------------------------------------------------------------
+//
+// XULDocument::ContextStack
+//
+
+XULDocument::ContextStack::ContextStack()
+ : mTop(nullptr), mDepth(0)
+{
+}
+
+XULDocument::ContextStack::~ContextStack()
+{
+ while (mTop) {
+ Entry* doomed = mTop;
+ mTop = mTop->mNext;
+ NS_IF_RELEASE(doomed->mElement);
+ delete doomed;
+ }
+}
+
+nsresult
+XULDocument::ContextStack::Push(nsXULPrototypeElement* aPrototype,
+ nsIContent* aElement)
+{
+ Entry* entry = new Entry;
+ entry->mPrototype = aPrototype;
+ entry->mElement = aElement;
+ NS_IF_ADDREF(entry->mElement);
+ entry->mIndex = 0;
+
+ entry->mNext = mTop;
+ mTop = entry;
+
+ ++mDepth;
+ return NS_OK;
+}
+
+nsresult
+XULDocument::ContextStack::Pop()
+{
+ if (mDepth == 0)
+ return NS_ERROR_UNEXPECTED;
+
+ Entry* doomed = mTop;
+ mTop = mTop->mNext;
+ --mDepth;
+
+ NS_IF_RELEASE(doomed->mElement);
+ delete doomed;
+ return NS_OK;
+}
+
+nsresult
+XULDocument::ContextStack::Peek(nsXULPrototypeElement** aPrototype,
+ nsIContent** aElement,
+ int32_t* aIndex)
+{
+ if (mDepth == 0)
+ return NS_ERROR_UNEXPECTED;
+
+ *aPrototype = mTop->mPrototype;
+ *aElement = mTop->mElement;
+ NS_IF_ADDREF(*aElement);
+ *aIndex = mTop->mIndex;
+
+ return NS_OK;
+}
+
+
+nsresult
+XULDocument::ContextStack::SetTopIndex(int32_t aIndex)
+{
+ if (mDepth == 0)
+ return NS_ERROR_UNEXPECTED;
+
+ mTop->mIndex = aIndex;
+ return NS_OK;
+}
+
+
+//----------------------------------------------------------------------
+//
+// Content model walking routines
+//
+
+nsresult
+XULDocument::PrepareToWalk()
+{
+ // Prepare to walk the mCurrentPrototype
+ nsresult rv;
+
+ // Keep an owning reference to the prototype document so that its
+ // elements aren't yanked from beneath us.
+ mPrototypes.AppendElement(mCurrentPrototype);
+
+ // Get the prototype's root element and initialize the context
+ // stack for the prototype walk.
+ nsXULPrototypeElement* proto = mCurrentPrototype->GetRootElement();
+
+ if (! proto) {
+ if (MOZ_LOG_TEST(gXULLog, LogLevel::Error)) {
+ nsCOMPtr<nsIURI> url = mCurrentPrototype->GetURI();
+
+ nsAutoCString urlspec;
+ rv = url->GetSpec(urlspec);
+ if (NS_FAILED(rv)) return rv;
+
+ MOZ_LOG(gXULLog, LogLevel::Error,
+ ("xul: error parsing '%s'", urlspec.get()));
+ }
+
+ return NS_OK;
+ }
+
+ uint32_t piInsertionPoint = 0;
+ if (mState != eState_Master) {
+ int32_t indexOfRoot = IndexOf(GetRootElement());
+ NS_ASSERTION(indexOfRoot >= 0,
+ "No root content when preparing to walk overlay!");
+ piInsertionPoint = indexOfRoot;
+ }
+
+ const nsTArray<RefPtr<nsXULPrototypePI> >& processingInstructions =
+ mCurrentPrototype->GetProcessingInstructions();
+
+ uint32_t total = processingInstructions.Length();
+ for (uint32_t i = 0; i < total; ++i) {
+ rv = CreateAndInsertPI(processingInstructions[i],
+ this, piInsertionPoint + i);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Now check the chrome registry for any additional overlays.
+ rv = AddChromeOverlays();
+ if (NS_FAILED(rv)) return rv;
+
+ // Do one-time initialization if we're preparing to walk the
+ // master document's prototype.
+ RefPtr<Element> root;
+
+ if (mState == eState_Master) {
+ // Add the root element
+ rv = CreateElementFromPrototype(proto, getter_AddRefs(root), true);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = AppendChildTo(root, false);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = AddElementToRefMap(root);
+ if (NS_FAILED(rv)) return rv;
+
+ // Block onload until we've finished building the complete
+ // document content model.
+ BlockOnload();
+ }
+
+ // There'd better not be anything on the context stack at this
+ // point! This is the basis case for our "induction" in
+ // ResumeWalk(), below, which'll assume that there's always a
+ // content element on the context stack if either 1) we're in the
+ // "master" document, or 2) we're in an overlay, and we've got
+ // more than one prototype element (the single, root "overlay"
+ // element) on the stack.
+ NS_ASSERTION(mContextStack.Depth() == 0, "something's on the context stack already");
+ if (mContextStack.Depth() != 0)
+ return NS_ERROR_UNEXPECTED;
+
+ rv = mContextStack.Push(proto, root);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+nsresult
+XULDocument::CreateAndInsertPI(const nsXULPrototypePI* aProtoPI,
+ nsINode* aParent, uint32_t aIndex)
+{
+ NS_PRECONDITION(aProtoPI, "null ptr");
+ NS_PRECONDITION(aParent, "null ptr");
+
+ RefPtr<ProcessingInstruction> node =
+ NS_NewXMLProcessingInstruction(mNodeInfoManager, aProtoPI->mTarget,
+ aProtoPI->mData);
+
+ nsresult rv;
+ if (aProtoPI->mTarget.EqualsLiteral("xml-stylesheet")) {
+ rv = InsertXMLStylesheetPI(aProtoPI, aParent, aIndex, node);
+ } else if (aProtoPI->mTarget.EqualsLiteral("xul-overlay")) {
+ rv = InsertXULOverlayPI(aProtoPI, aParent, aIndex, node);
+ } else {
+ // No special processing, just add the PI to the document.
+ rv = aParent->InsertChildAt(node, aIndex, false);
+ }
+
+ return rv;
+}
+
+nsresult
+XULDocument::InsertXMLStylesheetPI(const nsXULPrototypePI* aProtoPI,
+ nsINode* aParent,
+ uint32_t aIndex,
+ nsIContent* aPINode)
+{
+ nsCOMPtr<nsIStyleSheetLinkingElement> ssle(do_QueryInterface(aPINode));
+ NS_ASSERTION(ssle, "passed XML Stylesheet node does not "
+ "implement nsIStyleSheetLinkingElement!");
+
+ nsresult rv;
+
+ ssle->InitStyleLinkElement(false);
+ // We want to be notified when the style sheet finishes loading, so
+ // disable style sheet loading for now.
+ ssle->SetEnableUpdates(false);
+ ssle->OverrideBaseURI(mCurrentPrototype->GetURI());
+
+ rv = aParent->InsertChildAt(aPINode, aIndex, false);
+ if (NS_FAILED(rv)) return rv;
+
+ ssle->SetEnableUpdates(true);
+
+ // load the stylesheet if necessary, passing ourselves as
+ // nsICSSObserver
+ bool willNotify;
+ bool isAlternate;
+ rv = ssle->UpdateStyleSheet(this, &willNotify, &isAlternate);
+ if (NS_SUCCEEDED(rv) && willNotify && !isAlternate) {
+ ++mPendingSheets;
+ }
+
+ // Ignore errors from UpdateStyleSheet; we don't want failure to
+ // do that to break the XUL document load. But do propagate out
+ // NS_ERROR_OUT_OF_MEMORY.
+ if (rv == NS_ERROR_OUT_OF_MEMORY) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+XULDocument::InsertXULOverlayPI(const nsXULPrototypePI* aProtoPI,
+ nsINode* aParent,
+ uint32_t aIndex,
+ nsIContent* aPINode)
+{
+ nsresult rv;
+
+ rv = aParent->InsertChildAt(aPINode, aIndex, false);
+ if (NS_FAILED(rv)) return rv;
+
+ // xul-overlay PI is special only in prolog
+ if (!nsContentUtils::InProlog(aPINode)) {
+ return NS_OK;
+ }
+
+ nsAutoString href;
+ nsContentUtils::GetPseudoAttributeValue(aProtoPI->mData,
+ nsGkAtoms::href,
+ href);
+
+ // If there was no href, we can't do anything with this PI
+ if (href.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // Add the overlay to our list of overlays that need to be processed.
+ nsCOMPtr<nsIURI> uri;
+
+ rv = NS_NewURI(getter_AddRefs(uri), href, nullptr,
+ mCurrentPrototype->GetURI());
+ if (NS_SUCCEEDED(rv)) {
+ // We insert overlays into mUnloadedOverlays at the same index in
+ // document order, so they end up in the reverse of the document
+ // order in mUnloadedOverlays.
+ // This is needed because the code in ResumeWalk loads the overlays
+ // by processing the last item of mUnloadedOverlays and removing it
+ // from the array.
+ mUnloadedOverlays.InsertElementAt(0, uri);
+ rv = NS_OK;
+ } else if (rv == NS_ERROR_MALFORMED_URI) {
+ // The URL is bad, move along. Don't propagate for now.
+ // XXX report this to the Error Console (bug 359846)
+ rv = NS_OK;
+ }
+
+ return rv;
+}
+
+nsresult
+XULDocument::AddChromeOverlays()
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> docUri = mCurrentPrototype->GetURI();
+
+ /* overlays only apply to chrome or about URIs */
+ if (!IsOverlayAllowed(docUri)) return NS_OK;
+
+ nsCOMPtr<nsIXULOverlayProvider> chromeReg =
+ mozilla::services::GetXULOverlayProviderService();
+ // In embedding situations, the chrome registry may not provide overlays,
+ // or even exist at all; that's OK.
+ NS_ENSURE_TRUE(chromeReg, NS_OK);
+
+ nsCOMPtr<nsISimpleEnumerator> overlays;
+ rv = chromeReg->GetXULOverlays(docUri, getter_AddRefs(overlays));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool moreOverlays;
+ nsCOMPtr<nsISupports> next;
+ nsCOMPtr<nsIURI> uri;
+
+ while (NS_SUCCEEDED(rv = overlays->HasMoreElements(&moreOverlays)) &&
+ moreOverlays) {
+
+ rv = overlays->GetNext(getter_AddRefs(next));
+ if (NS_FAILED(rv) || !next) break;
+
+ uri = do_QueryInterface(next);
+ if (!uri) {
+ NS_ERROR("Chrome registry handed me a non-nsIURI object!");
+ continue;
+ }
+
+ // Same comment as in XULDocument::InsertXULOverlayPI
+ mUnloadedOverlays.InsertElementAt(0, uri);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+XULDocument::LoadOverlay(const nsAString& aURL, nsIObserver* aObserver)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ if (aObserver) {
+ nsIObserver* obs = nullptr;
+ if (!mOverlayLoadObservers) {
+ mOverlayLoadObservers = new nsInterfaceHashtable<nsURIHashKey,nsIObserver>;
+ }
+ obs = mOverlayLoadObservers->GetWeak(uri);
+
+ if (obs) {
+ // We don't support loading the same overlay twice into the same
+ // document - that doesn't make sense anyway.
+ return NS_ERROR_FAILURE;
+ }
+ mOverlayLoadObservers->Put(uri, aObserver);
+ }
+ bool shouldReturn, failureFromContent;
+ rv = LoadOverlayInternal(uri, true, &shouldReturn, &failureFromContent);
+ if (NS_FAILED(rv) && mOverlayLoadObservers)
+ mOverlayLoadObservers->Remove(uri); // remove the observer if LoadOverlayInternal generated an error
+ return rv;
+}
+
+nsresult
+XULDocument::LoadOverlayInternal(nsIURI* aURI, bool aIsDynamic,
+ bool* aShouldReturn,
+ bool* aFailureFromContent)
+{
+ nsresult rv;
+
+ *aShouldReturn = false;
+ *aFailureFromContent = false;
+
+ if (MOZ_LOG_TEST(gXULLog, LogLevel::Debug)) {
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetOriginalURI(getter_AddRefs(uri));
+
+ MOZ_LOG(gXULLog, LogLevel::Debug,
+ ("xul: %s loading overlay %s",
+ uri ? uri->GetSpecOrDefault().get() : "",
+ aURI->GetSpecOrDefault().get()));
+ }
+
+ if (aIsDynamic)
+ mResolutionPhase = nsForwardReference::eStart;
+
+ // Look in the prototype cache for the prototype document with
+ // the specified overlay URI. Only use the cache if the containing
+ // document is chrome otherwise it may not have a system principal and
+ // the cached document will, see bug 565610.
+ bool overlayIsChrome = IsChromeURI(aURI);
+ bool documentIsChrome = IsChromeURI(mDocumentURI);
+ mCurrentPrototype = overlayIsChrome && documentIsChrome ?
+ nsXULPrototypeCache::GetInstance()->GetPrototype(aURI) : nullptr;
+
+ // Same comment as nsChromeProtocolHandler::NewChannel and
+ // XULDocument::StartDocumentLoad
+ // - Ben Goodger
+ //
+ // We don't abort on failure here because there are too many valid
+ // cases that can return failure, and the null-ness of |proto| is
+ // enough to trigger the fail-safe parse-from-disk solution.
+ // Example failure cases (for reference) include:
+ //
+ // NS_ERROR_NOT_AVAILABLE: the URI was not found in the FastLoad file,
+ // parse from disk
+ // other: the FastLoad file, XUL.mfl, could not be found, probably
+ // due to being accessed before a profile has been selected
+ // (e.g. loading chrome for the profile manager itself).
+ // The .xul file must be parsed from disk.
+
+ bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
+ if (useXULCache && mCurrentPrototype) {
+ bool loaded;
+ rv = mCurrentPrototype->AwaitLoadDone(this, &loaded);
+ if (NS_FAILED(rv)) return rv;
+
+ if (! loaded) {
+ // Return to the main event loop and eagerly await the
+ // prototype overlay load's completion. When the content
+ // sink completes, it will trigger an EndLoad(), which'll
+ // wind us back up here, in ResumeWalk().
+ *aShouldReturn = true;
+ return NS_OK;
+ }
+
+ MOZ_LOG(gXULLog, LogLevel::Debug, ("xul: overlay was cached"));
+
+ // Found the overlay's prototype in the cache, fully loaded. If
+ // this is a dynamic overlay, this will call ResumeWalk.
+ // Otherwise, we'll return to ResumeWalk, which called us.
+ return OnPrototypeLoadDone(aIsDynamic);
+ }
+ else {
+ // Not there. Initiate a load.
+ MOZ_LOG(gXULLog, LogLevel::Debug, ("xul: overlay was not cached"));
+
+ if (mIsGoingAway) {
+ MOZ_LOG(gXULLog, LogLevel::Debug, ("xul: ...and document already destroyed"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // We'll set the right principal on the proto doc when we get
+ // OnStartRequest from the parser, so just pass in a null principal for
+ // now.
+ nsCOMPtr<nsIParser> parser;
+ rv = PrepareToLoadPrototype(aURI, "view", nullptr, getter_AddRefs(parser));
+ if (NS_FAILED(rv)) return rv;
+
+ // Predicate mIsWritingFastLoad on the XUL cache being enabled,
+ // so we don't have to re-check whether the cache is enabled all
+ // the time.
+ mIsWritingFastLoad = useXULCache;
+
+ nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(parser);
+ if (! listener)
+ return NS_ERROR_UNEXPECTED;
+
+ // Add an observer to the parser; this'll get called when
+ // Necko fires its On[Start|Stop]Request() notifications,
+ // and will let us recover from a missing overlay.
+ RefPtr<ParserObserver> parserObserver =
+ new ParserObserver(this, mCurrentPrototype);
+ parser->Parse(aURI, parserObserver);
+ parserObserver = nullptr;
+
+ nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
+ nsCOMPtr<nsIChannel> channel;
+ // Set the owner of the channel to be our principal so
+ // that the overlay's JSObjects etc end up being created
+ // with the right principal and in the correct
+ // compartment.
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ aURI,
+ NodePrincipal(),
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS |
+ nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
+ nsIContentPolicy::TYPE_OTHER,
+ group);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = channel->AsyncOpen2(listener);
+ }
+
+ if (NS_FAILED(rv)) {
+ // Abandon this prototype
+ mCurrentPrototype = nullptr;
+
+ // The parser won't get an OnStartRequest and
+ // OnStopRequest, so it needs a Terminate.
+ parser->Terminate();
+
+ // Just move on to the next overlay.
+ ReportMissingOverlay(aURI);
+
+ // XXX the error could indicate an internal error as well...
+ *aFailureFromContent = true;
+ return rv;
+ }
+
+ // If it's a 'chrome:' prototype document, then put it into
+ // the prototype cache; other XUL documents will be reloaded
+ // each time. We must do this after AsyncOpen,
+ // or chrome code will wrongly create a cached chrome channel
+ // instead of a real one. Prototypes are only cached when the
+ // document to be overlayed is chrome to avoid caching overlay
+ // scripts with incorrect principals, see bug 565610.
+ if (useXULCache && overlayIsChrome && documentIsChrome) {
+ nsXULPrototypeCache::GetInstance()->PutPrototype(mCurrentPrototype);
+ }
+
+ // Return to the main event loop and eagerly await the
+ // overlay load's completion. When the content sink
+ // completes, it will trigger an EndLoad(), which'll wind
+ // us back in ResumeWalk().
+ if (!aIsDynamic)
+ *aShouldReturn = true;
+ }
+ return NS_OK;
+}
+
+nsresult
+XULDocument::ResumeWalk()
+{
+ // Walk the prototype and build the delegate content model. The
+ // walk is performed in a top-down, left-to-right fashion. That
+ // is, a parent is built before any of its children; a node is
+ // only built after all of its siblings to the left are fully
+ // constructed.
+ //
+ // It is interruptable so that transcluded documents (e.g.,
+ // <html:script src="..." />) can be properly re-loaded if the
+ // cached copy of the document becomes stale.
+ nsresult rv;
+ nsCOMPtr<nsIURI> overlayURI =
+ mCurrentPrototype ? mCurrentPrototype->GetURI() : nullptr;
+
+ while (1) {
+ // Begin (or resume) walking the current prototype.
+
+ while (mContextStack.Depth() > 0) {
+ // Look at the top of the stack to determine what we're
+ // currently working on.
+ // This will always be a node already constructed and
+ // inserted to the actual document.
+ nsXULPrototypeElement* proto;
+ nsCOMPtr<nsIContent> element;
+ int32_t indx; // all children of proto before indx (not
+ // inclusive) have already been constructed
+ rv = mContextStack.Peek(&proto, getter_AddRefs(element), &indx);
+ if (NS_FAILED(rv)) return rv;
+
+ if (indx >= (int32_t)proto->mChildren.Length()) {
+ if (element) {
+ // We've processed all of the prototype's children. If
+ // we're in the master prototype, do post-order
+ // document-level hookup. (An overlay will get its
+ // document hookup done when it's successfully
+ // resolved.)
+ if (mState == eState_Master) {
+ AddElementToDocumentPost(element->AsElement());
+
+ if (element->NodeInfo()->Equals(nsGkAtoms::style,
+ kNameSpaceID_XHTML) ||
+ element->NodeInfo()->Equals(nsGkAtoms::style,
+ kNameSpaceID_SVG)) {
+ // XXX sucks that we have to do this -
+ // see bug 370111
+ nsCOMPtr<nsIStyleSheetLinkingElement> ssle =
+ do_QueryInterface(element);
+ NS_ASSERTION(ssle, "<html:style> doesn't implement "
+ "nsIStyleSheetLinkingElement?");
+ bool willNotify;
+ bool isAlternate;
+ ssle->UpdateStyleSheet(nullptr, &willNotify,
+ &isAlternate);
+ }
+ }
+ }
+ // Now pop the context stack back up to the parent
+ // element and continue the prototype walk.
+ mContextStack.Pop();
+ continue;
+ }
+
+ // Grab the next child, and advance the current context stack
+ // to the next sibling to our right.
+ nsXULPrototypeNode* childproto = proto->mChildren[indx];
+ mContextStack.SetTopIndex(++indx);
+
+ // Whether we're in the "first ply" of an overlay:
+ // the "hookup" nodes. In the case !processingOverlayHookupNodes,
+ // we're in the master document -or- we're in an overlay, and far
+ // enough down into the overlay's content that we can simply build
+ // the delegates and attach them to the parent node.
+ bool processingOverlayHookupNodes = (mState == eState_Overlay) &&
+ (mContextStack.Depth() == 1);
+
+ NS_ASSERTION(element || processingOverlayHookupNodes,
+ "no element on context stack");
+
+ switch (childproto->mType) {
+ case nsXULPrototypeNode::eType_Element: {
+ // An 'element', which may contain more content.
+ nsXULPrototypeElement* protoele =
+ static_cast<nsXULPrototypeElement*>(childproto);
+
+ RefPtr<Element> child;
+
+ if (!processingOverlayHookupNodes) {
+ rv = CreateElementFromPrototype(protoele,
+ getter_AddRefs(child),
+ false);
+ if (NS_FAILED(rv)) return rv;
+
+ // ...and append it to the content model.
+ rv = element->AppendChildTo(child, false);
+ if (NS_FAILED(rv)) return rv;
+
+ // If we're only restoring persisted things on
+ // some elements, store the ID here to do that.
+ if (mRestrictPersistence) {
+ nsIAtom* id = child->GetID();
+ if (id) {
+ mPersistenceIds.PutEntry(nsDependentAtomString(id));
+ }
+ }
+
+ // do pre-order document-level hookup, but only if
+ // we're in the master document. For an overlay,
+ // this will happen when the overlay is
+ // successfully resolved.
+ if (mState == eState_Master)
+ AddElementToDocumentPre(child);
+ }
+ else {
+ // We're in the "first ply" of an overlay: the
+ // "hookup" nodes. Create an 'overlay' element so
+ // that we can continue to build content, and
+ // enter a forward reference so we can hook it up
+ // later.
+ rv = CreateOverlayElement(protoele, getter_AddRefs(child));
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // If it has children, push the element onto the context
+ // stack and begin to process them.
+ if (protoele->mChildren.Length() > 0) {
+ rv = mContextStack.Push(protoele, child);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else {
+ if (mState == eState_Master) {
+ // If there are no children, and we're in the
+ // master document, do post-order document hookup
+ // immediately.
+ AddElementToDocumentPost(child);
+ }
+ }
+ }
+ break;
+
+ case nsXULPrototypeNode::eType_Script: {
+ // A script reference. Execute the script immediately;
+ // this may have side effects in the content model.
+ nsXULPrototypeScript* scriptproto =
+ static_cast<nsXULPrototypeScript*>(childproto);
+
+ if (scriptproto->mSrcURI) {
+ // A transcluded script reference; this may
+ // "block" our prototype walk if the script isn't
+ // cached, or the cached copy of the script is
+ // stale and must be reloaded.
+ bool blocked;
+ rv = LoadScript(scriptproto, &blocked);
+ // If the script cannot be loaded, just keep going!
+
+ if (NS_SUCCEEDED(rv) && blocked)
+ return NS_OK;
+ }
+ else if (scriptproto->HasScriptObject()) {
+ // An inline script
+ rv = ExecuteScript(scriptproto);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+ break;
+
+ case nsXULPrototypeNode::eType_Text: {
+ // A simple text node.
+
+ if (!processingOverlayHookupNodes) {
+ // This does mean that text nodes that are direct children
+ // of <overlay> get ignored.
+
+ RefPtr<nsTextNode> text =
+ new nsTextNode(mNodeInfoManager);
+
+ nsXULPrototypeText* textproto =
+ static_cast<nsXULPrototypeText*>(childproto);
+ text->SetText(textproto->mValue, false);
+
+ rv = element->AppendChildTo(text, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ break;
+
+ case nsXULPrototypeNode::eType_PI: {
+ nsXULPrototypePI* piProto =
+ static_cast<nsXULPrototypePI*>(childproto);
+
+ // <?xul-overlay?> and <?xml-stylesheet?> don't have effect
+ // outside the prolog, like they used to. Issue a warning.
+
+ if (piProto->mTarget.EqualsLiteral("xml-stylesheet") ||
+ piProto->mTarget.EqualsLiteral("xul-overlay")) {
+
+ const char16_t* params[] = { piProto->mTarget.get() };
+
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("XUL Document"), nullptr,
+ nsContentUtils::eXUL_PROPERTIES,
+ "PINotInProlog",
+ params, ArrayLength(params),
+ overlayURI);
+ }
+
+ nsIContent* parent = processingOverlayHookupNodes ?
+ GetRootElement() : element.get();
+
+ if (parent) {
+ // an inline script could have removed the root element
+ rv = CreateAndInsertPI(piProto, parent,
+ parent->GetChildCount());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ break;
+
+ default:
+ NS_NOTREACHED("Unexpected nsXULPrototypeNode::Type value");
+ }
+ }
+
+ // Once we get here, the context stack will have been
+ // depleted. That means that the entire prototype has been
+ // walked and content has been constructed.
+
+ // If we're not already, mark us as now processing overlays.
+ mState = eState_Overlay;
+
+ // If there are no overlay URIs, then we're done.
+ uint32_t count = mUnloadedOverlays.Length();
+ if (! count)
+ break;
+
+ nsCOMPtr<nsIURI> uri = mUnloadedOverlays[count-1];
+ mUnloadedOverlays.RemoveElementAt(count - 1);
+
+ bool shouldReturn, failureFromContent;
+ rv = LoadOverlayInternal(uri, false, &shouldReturn,
+ &failureFromContent);
+ if (failureFromContent)
+ // The failure |rv| was the result of a problem in the content
+ // rather than an unexpected problem in our implementation, so
+ // just continue with the next overlay.
+ continue;
+ if (NS_FAILED(rv))
+ return rv;
+ if (mOverlayLoadObservers) {
+ nsIObserver *obs = mOverlayLoadObservers->GetWeak(overlayURI);
+ if (obs) {
+ // This overlay has an unloaded overlay, so it will never
+ // notify. The best we can do is to notify for the unloaded
+ // overlay instead, assuming nobody is already notifiable
+ // for it. Note that this will confuse the observer.
+ if (!mOverlayLoadObservers->GetWeak(uri))
+ mOverlayLoadObservers->Put(uri, obs);
+ mOverlayLoadObservers->Remove(overlayURI);
+ }
+ }
+ if (shouldReturn)
+ return NS_OK;
+ overlayURI.swap(uri);
+ }
+
+ // If we get here, there is nothing left for us to walk. The content
+ // model is built and ready for layout.
+ rv = ResolveForwardReferences();
+ if (NS_FAILED(rv)) return rv;
+
+ ApplyPersistentAttributes();
+
+ mStillWalking = false;
+ if (mPendingSheets == 0) {
+ rv = DoneWalking();
+ }
+ return rv;
+}
+
+nsresult
+XULDocument::DoneWalking()
+{
+ NS_PRECONDITION(mPendingSheets == 0, "there are sheets to be loaded");
+ NS_PRECONDITION(!mStillWalking, "walk not done");
+
+ // XXXldb This is where we should really be setting the chromehidden
+ // attribute.
+
+ {
+ mozAutoDocUpdate updateBatch(this, UPDATE_STYLE, true);
+ uint32_t count = mOverlaySheets.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ AddStyleSheet(mOverlaySheets[i]);
+ }
+ }
+
+ mOverlaySheets.Clear();
+
+ if (!mDocumentLoaded) {
+ // Make sure we don't reenter here from StartLayout(). Note that
+ // setting mDocumentLoaded to true here means that if StartLayout()
+ // causes ResumeWalk() to be reentered, we'll take the other branch of
+ // the |if (!mDocumentLoaded)| check above and since
+ // mInitialLayoutComplete will be false will follow the else branch
+ // there too. See the big comment there for how such reentry can
+ // happen.
+ mDocumentLoaded = true;
+
+ NotifyPossibleTitleChange(false);
+
+ // Before starting layout, check whether we're a toplevel chrome
+ // window. If we are, set our chrome flags now, so that we don't have
+ // to restyle the whole frame tree after StartLayout.
+ nsCOMPtr<nsIDocShellTreeItem> item = GetDocShell();
+ if (item) {
+ nsCOMPtr<nsIDocShellTreeOwner> owner;
+ item->GetTreeOwner(getter_AddRefs(owner));
+ nsCOMPtr<nsIXULWindow> xulWin = do_GetInterface(owner);
+ if (xulWin) {
+ nsCOMPtr<nsIDocShell> xulWinShell;
+ xulWin->GetDocShell(getter_AddRefs(xulWinShell));
+ if (SameCOMIdentity(xulWinShell, item)) {
+ // We're the chrome document! Apply our chrome flags now.
+ xulWin->ApplyChromeFlags();
+ }
+ }
+ }
+
+ StartLayout();
+
+ if (mIsWritingFastLoad && IsChromeURI(mDocumentURI))
+ nsXULPrototypeCache::GetInstance()->WritePrototype(mMasterPrototype);
+
+ NS_ASSERTION(mDelayFrameLoaderInitialization,
+ "mDelayFrameLoaderInitialization should be true!");
+ mDelayFrameLoaderInitialization = false;
+ NS_WARNING_ASSERTION(
+ mUpdateNestLevel == 0,
+ "Constructing XUL document in middle of an update?");
+ if (mUpdateNestLevel == 0) {
+ MaybeInitializeFinalizeFrameLoaders();
+ }
+
+ NS_DOCUMENT_NOTIFY_OBSERVERS(EndLoad, (this));
+
+ // DispatchContentLoadedEvents undoes the onload-blocking we
+ // did in PrepareToWalk().
+ DispatchContentLoadedEvents();
+
+ mInitialLayoutComplete = true;
+
+ // Walk the set of pending load notifications and notify any observers.
+ // See below for detail.
+ if (mPendingOverlayLoadNotifications) {
+ nsInterfaceHashtable<nsURIHashKey,nsIObserver>* observers =
+ mOverlayLoadObservers.get();
+ for (auto iter = mPendingOverlayLoadNotifications->Iter();
+ !iter.Done();
+ iter.Next()) {
+ nsIURI* aKey = iter.Key();
+ iter.Data()->Observe(aKey, "xul-overlay-merged",
+ EmptyString().get());
+
+ if (observers) {
+ observers->Remove(aKey);
+ }
+
+ iter.Remove();
+ }
+ }
+ }
+ else {
+ if (mOverlayLoadObservers) {
+ nsCOMPtr<nsIURI> overlayURI = mCurrentPrototype->GetURI();
+ nsCOMPtr<nsIObserver> obs;
+ if (mInitialLayoutComplete) {
+ // We have completed initial layout, so just send the notification.
+ mOverlayLoadObservers->Get(overlayURI, getter_AddRefs(obs));
+ if (obs)
+ obs->Observe(overlayURI, "xul-overlay-merged", EmptyString().get());
+ mOverlayLoadObservers->Remove(overlayURI);
+ }
+ else {
+ // If we have not yet displayed the document for the first time
+ // (i.e. we came in here as the result of a dynamic overlay load
+ // which was spawned by a binding-attached event caused by
+ // StartLayout() on the master prototype - we must remember that
+ // this overlay has been merged and tell the listeners after
+ // StartLayout() is completely finished rather than doing so
+ // immediately - otherwise we may be executing code that needs to
+ // access XBL Binding implementations on nodes for which frames
+ // have not yet been constructed because their bindings have not
+ // yet been attached. This can be a race condition because dynamic
+ // overlay loading can take varying amounts of time depending on
+ // whether or not the overlay prototype is in the XUL cache. The
+ // most likely effect of this bug is odd UI initialization due to
+ // methods and properties that do not work.
+ // XXXbz really, we shouldn't be firing binding constructors
+ // until after StartLayout returns!
+
+ if (!mPendingOverlayLoadNotifications) {
+ mPendingOverlayLoadNotifications =
+ new nsInterfaceHashtable<nsURIHashKey,nsIObserver>;
+ }
+
+ mPendingOverlayLoadNotifications->Get(overlayURI, getter_AddRefs(obs));
+ if (!obs) {
+ mOverlayLoadObservers->Get(overlayURI, getter_AddRefs(obs));
+ NS_ASSERTION(obs, "null overlay load observer?");
+ mPendingOverlayLoadNotifications->Put(overlayURI, obs);
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULDocument::StyleSheetLoaded(StyleSheet* aSheet,
+ bool aWasAlternate,
+ nsresult aStatus)
+{
+ if (!aWasAlternate) {
+ // Don't care about when alternate sheets finish loading
+
+ NS_ASSERTION(mPendingSheets > 0,
+ "Unexpected StyleSheetLoaded notification");
+
+ --mPendingSheets;
+
+ if (!mStillWalking && mPendingSheets == 0) {
+ return DoneWalking();
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+XULDocument::MaybeBroadcast()
+{
+ // Only broadcast when not in an update and when safe to run scripts.
+ if (mUpdateNestLevel == 0 &&
+ (mDelayedAttrChangeBroadcasts.Length() ||
+ mDelayedBroadcasters.Length())) {
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ if (!mInDestructor) {
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod(this, &XULDocument::MaybeBroadcast));
+ }
+ return;
+ }
+ if (!mHandlingDelayedAttrChange) {
+ mHandlingDelayedAttrChange = true;
+ for (uint32_t i = 0; i < mDelayedAttrChangeBroadcasts.Length(); ++i) {
+ nsIAtom* attrName = mDelayedAttrChangeBroadcasts[i].mAttrName;
+ if (mDelayedAttrChangeBroadcasts[i].mNeedsAttrChange) {
+ nsCOMPtr<nsIContent> listener =
+ do_QueryInterface(mDelayedAttrChangeBroadcasts[i].mListener);
+ const nsString& value = mDelayedAttrChangeBroadcasts[i].mAttr;
+ if (mDelayedAttrChangeBroadcasts[i].mSetAttr) {
+ listener->SetAttr(kNameSpaceID_None, attrName, value,
+ true);
+ } else {
+ listener->UnsetAttr(kNameSpaceID_None, attrName,
+ true);
+ }
+ }
+ ExecuteOnBroadcastHandlerFor(mDelayedAttrChangeBroadcasts[i].mBroadcaster,
+ mDelayedAttrChangeBroadcasts[i].mListener,
+ attrName);
+ }
+ mDelayedAttrChangeBroadcasts.Clear();
+ mHandlingDelayedAttrChange = false;
+ }
+
+ uint32_t length = mDelayedBroadcasters.Length();
+ if (length) {
+ bool oldValue = mHandlingDelayedBroadcasters;
+ mHandlingDelayedBroadcasters = true;
+ nsTArray<nsDelayedBroadcastUpdate> delayedBroadcasters;
+ mDelayedBroadcasters.SwapElements(delayedBroadcasters);
+ for (uint32_t i = 0; i < length; ++i) {
+ SynchronizeBroadcastListener(delayedBroadcasters[i].mBroadcaster,
+ delayedBroadcasters[i].mListener,
+ delayedBroadcasters[i].mAttr);
+ }
+ mHandlingDelayedBroadcasters = oldValue;
+ }
+ }
+}
+
+void
+XULDocument::EndUpdate(nsUpdateType aUpdateType)
+{
+ XMLDocument::EndUpdate(aUpdateType);
+
+ MaybeBroadcast();
+}
+
+void
+XULDocument::ReportMissingOverlay(nsIURI* aURI)
+{
+ NS_PRECONDITION(aURI, "Must have a URI");
+
+ NS_ConvertUTF8toUTF16 utfSpec(aURI->GetSpecOrDefault());
+ const char16_t* params[] = { utfSpec.get() };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("XUL Document"), this,
+ nsContentUtils::eXUL_PROPERTIES,
+ "MissingOverlay",
+ params, ArrayLength(params));
+}
+
+nsresult
+XULDocument::LoadScript(nsXULPrototypeScript* aScriptProto, bool* aBlock)
+{
+ // Load a transcluded script
+ nsresult rv;
+
+ bool isChromeDoc = IsChromeURI(mDocumentURI);
+
+ if (isChromeDoc && aScriptProto->HasScriptObject()) {
+ rv = ExecuteScript(aScriptProto);
+
+ // Ignore return value from execution, and don't block
+ *aBlock = false;
+ return NS_OK;
+ }
+
+ // Try the XUL script cache, in case two XUL documents source the same
+ // .js file (e.g., strres.js from navigator.xul and utilityOverlay.xul).
+ // XXXbe the cache relies on aScriptProto's GC root!
+ bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
+
+ if (isChromeDoc && useXULCache) {
+ JSScript* newScriptObject =
+ nsXULPrototypeCache::GetInstance()->GetScript(
+ aScriptProto->mSrcURI);
+ if (newScriptObject) {
+ // The script language for a proto must remain constant - we
+ // can't just change it for this unexpected language.
+ aScriptProto->Set(newScriptObject);
+ }
+
+ if (aScriptProto->HasScriptObject()) {
+ rv = ExecuteScript(aScriptProto);
+
+ // Ignore return value from execution, and don't block
+ *aBlock = false;
+ return NS_OK;
+ }
+ }
+
+ // Release script objects from FastLoad since we decided against using them
+ aScriptProto->UnlinkJSObjects();
+
+ // Set the current script prototype so that OnStreamComplete can report
+ // the right file if there are errors in the script.
+ NS_ASSERTION(!mCurrentScriptProto,
+ "still loading a script when starting another load?");
+ mCurrentScriptProto = aScriptProto;
+
+ if (isChromeDoc && aScriptProto->mSrcLoading) {
+ // Another XULDocument load has started, which is still in progress.
+ // Remember to ResumeWalk this document when the load completes.
+ mNextSrcLoadWaiter = aScriptProto->mSrcLoadWaiters;
+ aScriptProto->mSrcLoadWaiters = this;
+ NS_ADDREF_THIS();
+ }
+ else {
+ nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
+
+ // Note: the loader will keep itself alive while it's loading.
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(getter_AddRefs(loader),
+ aScriptProto->mSrcURI,
+ this, // aObserver
+ this, // aRequestingContext
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS,
+ nsIContentPolicy::TYPE_INTERNAL_SCRIPT,
+ group);
+
+ if (NS_FAILED(rv)) {
+ mCurrentScriptProto = nullptr;
+ return rv;
+ }
+
+ aScriptProto->mSrcLoading = true;
+ }
+
+ // Block until OnStreamComplete resumes us.
+ *aBlock = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULDocument::OnStreamComplete(nsIStreamLoader* aLoader,
+ nsISupports* context,
+ nsresult aStatus,
+ uint32_t stringLen,
+ const uint8_t* string)
+{
+ nsCOMPtr<nsIRequest> request;
+ aLoader->GetRequest(getter_AddRefs(request));
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+
+#ifdef DEBUG
+ // print a load error on bad status
+ if (NS_FAILED(aStatus)) {
+ if (channel) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri) {
+ printf("Failed to load %s\n", uri->GetSpecOrDefault().get());
+ }
+ }
+ }
+#endif
+
+ // This is the completion routine that will be called when a
+ // transcluded script completes. Compile and execute the script
+ // if the load was successful, then continue building content
+ // from the prototype.
+ nsresult rv = aStatus;
+
+ NS_ASSERTION(mCurrentScriptProto && mCurrentScriptProto->mSrcLoading,
+ "script source not loading on unichar stream complete?");
+ if (!mCurrentScriptProto) {
+ // XXX Wallpaper for bug 270042
+ return NS_OK;
+ }
+
+ if (NS_SUCCEEDED(aStatus)) {
+ // If the including XUL document is a FastLoad document, and we're
+ // compiling an out-of-line script (one with src=...), then we must
+ // be writing a new FastLoad file. If we were reading this script
+ // from the FastLoad file, XULContentSinkImpl::OpenScript (over in
+ // nsXULContentSink.cpp) would have already deserialized a non-null
+ // script->mScriptObject, causing control flow at the top of LoadScript
+ // not to reach here.
+ nsCOMPtr<nsIURI> uri = mCurrentScriptProto->mSrcURI;
+
+ // XXX should also check nsIHttpChannel::requestSucceeded
+
+ MOZ_ASSERT(!mOffThreadCompiling && (mOffThreadCompileStringLength == 0 &&
+ !mOffThreadCompileStringBuf),
+ "XULDocument can't load multiple scripts at once");
+
+ rv = nsScriptLoader::ConvertToUTF16(channel, string, stringLen,
+ EmptyString(), this,
+ mOffThreadCompileStringBuf,
+ mOffThreadCompileStringLength);
+ if (NS_SUCCEEDED(rv)) {
+ // Attempt to give ownership of the buffer to the JS engine. If
+ // we hit offthread compilation, however, we will have to take it
+ // back below in order to keep the memory alive until compilation
+ // completes.
+ JS::SourceBufferHolder srcBuf(mOffThreadCompileStringBuf,
+ mOffThreadCompileStringLength,
+ JS::SourceBufferHolder::GiveOwnership);
+ mOffThreadCompileStringBuf = nullptr;
+ mOffThreadCompileStringLength = 0;
+
+ rv = mCurrentScriptProto->Compile(srcBuf, uri, 1, this, this);
+ if (NS_SUCCEEDED(rv) && !mCurrentScriptProto->HasScriptObject()) {
+ // We will be notified via OnOffThreadCompileComplete when the
+ // compile finishes. Keep the contents of the compiled script
+ // alive until the compilation finishes.
+ mOffThreadCompiling = true;
+ // If the JS engine did not take the source buffer, then take
+ // it back here to ensure it remains alive.
+ mOffThreadCompileStringBuf = srcBuf.take();
+ if (mOffThreadCompileStringBuf) {
+ mOffThreadCompileStringLength = srcBuf.length();
+ }
+ BlockOnload();
+ return NS_OK;
+ }
+ }
+ }
+
+ return OnScriptCompileComplete(mCurrentScriptProto->GetScriptObject(), rv);
+}
+
+NS_IMETHODIMP
+XULDocument::OnScriptCompileComplete(JSScript* aScript, nsresult aStatus)
+{
+ // When compiling off thread the script will not have been attached to the
+ // script proto yet.
+ if (aScript && !mCurrentScriptProto->HasScriptObject())
+ mCurrentScriptProto->Set(aScript);
+
+ // Allow load events to be fired once off thread compilation finishes.
+ if (mOffThreadCompiling) {
+ mOffThreadCompiling = false;
+ UnblockOnload(false);
+ }
+
+ // After compilation finishes the script's characters are no longer needed.
+ if (mOffThreadCompileStringBuf) {
+ js_free(mOffThreadCompileStringBuf);
+ mOffThreadCompileStringBuf = nullptr;
+ mOffThreadCompileStringLength = 0;
+ }
+
+ // Clear mCurrentScriptProto now, but save it first for use below in
+ // the execute code, and in the while loop that resumes walks of other
+ // documents that raced to load this script.
+ nsXULPrototypeScript* scriptProto = mCurrentScriptProto;
+ mCurrentScriptProto = nullptr;
+
+ // Clear the prototype's loading flag before executing the script or
+ // resuming document walks, in case any of those control flows starts a
+ // new script load.
+ scriptProto->mSrcLoading = false;
+
+ nsresult rv = aStatus;
+ if (NS_SUCCEEDED(rv)) {
+ rv = ExecuteScript(scriptProto);
+
+ // If the XUL cache is enabled, save the script object there in
+ // case different XUL documents source the same script.
+ //
+ // But don't save the script in the cache unless the master XUL
+ // document URL is a chrome: URL. It is valid for a URL such as
+ // about:config to translate into a master document URL, whose
+ // prototype document nodes -- including prototype scripts that
+ // hold GC roots protecting their mJSObject pointers -- are not
+ // cached in the XUL prototype cache. See StartDocumentLoad,
+ // the fillXULCache logic.
+ //
+ // A document such as about:config is free to load a script via
+ // a URL such as chrome://global/content/config.js, and we must
+ // not cache that script object without a prototype cache entry
+ // containing a companion nsXULPrototypeScript node that owns a
+ // GC root protecting the script object. Otherwise, the script
+ // cache entry will dangle once the uncached prototype document
+ // is released when its owning XULDocument is unloaded.
+ //
+ // (See http://bugzilla.mozilla.org/show_bug.cgi?id=98207 for
+ // the true crime story.)
+ bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
+
+ if (useXULCache && IsChromeURI(mDocumentURI) && scriptProto->HasScriptObject()) {
+ JS::Rooted<JSScript*> script(RootingCx(), scriptProto->GetScriptObject());
+ nsXULPrototypeCache::GetInstance()->PutScript(
+ scriptProto->mSrcURI, script);
+ }
+
+ if (mIsWritingFastLoad && mCurrentPrototype != mMasterPrototype) {
+ // If we are loading an overlay script, try to serialize
+ // it to the FastLoad file here. Master scripts will be
+ // serialized when the master prototype document gets
+ // written, at the bottom of ResumeWalk. That way, master
+ // out-of-line scripts are serialized in the same order that
+ // they'll be read, in the FastLoad file, which reduces the
+ // number of seeks that dump the underlying stream's buffer.
+ //
+ // Ignore the return value, as we don't need to propagate
+ // a failure to write to the FastLoad file, because this
+ // method aborts that whole process on error.
+ scriptProto->SerializeOutOfLine(nullptr, mCurrentPrototype);
+ }
+ // ignore any evaluation errors
+ }
+
+ rv = ResumeWalk();
+
+ // Load a pointer to the prototype-script's list of XULDocuments who
+ // raced to load the same script
+ XULDocument** docp = &scriptProto->mSrcLoadWaiters;
+
+ // Resume walking other documents that waited for this one's load, first
+ // executing the script we just compiled, in each doc's script context
+ XULDocument* doc;
+ while ((doc = *docp) != nullptr) {
+ NS_ASSERTION(doc->mCurrentScriptProto == scriptProto,
+ "waiting for wrong script to load?");
+ doc->mCurrentScriptProto = nullptr;
+
+ // Unlink doc from scriptProto's list before executing and resuming
+ *docp = doc->mNextSrcLoadWaiter;
+ doc->mNextSrcLoadWaiter = nullptr;
+
+ // Execute only if we loaded and compiled successfully, then resume
+ if (NS_SUCCEEDED(aStatus) && scriptProto->HasScriptObject()) {
+ doc->ExecuteScript(scriptProto);
+ }
+ doc->ResumeWalk();
+ NS_RELEASE(doc);
+ }
+
+ return rv;
+}
+
+nsresult
+XULDocument::ExecuteScript(nsXULPrototypeScript *aScript)
+{
+ NS_PRECONDITION(aScript != nullptr, "null ptr");
+ NS_ENSURE_TRUE(aScript, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE(mScriptGlobalObject, NS_ERROR_NOT_INITIALIZED);
+
+ nsresult rv;
+ rv = mScriptGlobalObject->EnsureScriptEnvironment();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Execute the precompiled script with the given version
+ nsAutoMicroTask mt;
+
+ // We're about to run script via JS::CloneAndExecuteScript, so we need an
+ // AutoEntryScript. This is Gecko specific and not in any spec.
+ AutoEntryScript aes(mScriptGlobalObject, "precompiled XUL <script> element");
+ JSContext* cx = aes.cx();
+
+ JS::Rooted<JSScript*> scriptObject(cx, aScript->GetScriptObject());
+ NS_ENSURE_TRUE(scriptObject, NS_ERROR_UNEXPECTED);
+
+ JS::Rooted<JSObject*> baseGlobal(cx, JS::CurrentGlobalOrNull(cx));
+ NS_ENSURE_TRUE(xpc::Scriptability::Get(baseGlobal).Allowed(), NS_OK);
+
+ JSAddonId* addonId = mCurrentPrototype ? MapURIToAddonID(mCurrentPrototype->GetURI()) : nullptr;
+ JS::Rooted<JSObject*> global(cx, xpc::GetAddonScope(cx, baseGlobal, addonId));
+ NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
+
+ JS::ExposeObjectToActiveJS(global);
+ JSAutoCompartment ac(cx, global);
+
+ // The script is in the compilation scope. Clone it into the target scope
+ // and execute it. On failure, ~AutoScriptEntry will handle exceptions, so
+ // there is no need to manually check the return value.
+ JS::RootedValue rval(cx);
+ JS::CloneAndExecuteScript(cx, scriptObject, &rval);
+
+ return NS_OK;
+}
+
+
+nsresult
+XULDocument::CreateElementFromPrototype(nsXULPrototypeElement* aPrototype,
+ Element** aResult,
+ bool aIsRoot)
+{
+ // Create a content model element from a prototype element.
+ NS_PRECONDITION(aPrototype != nullptr, "null ptr");
+ if (! aPrototype)
+ return NS_ERROR_NULL_POINTER;
+
+ *aResult = nullptr;
+ nsresult rv = NS_OK;
+
+ if (MOZ_LOG_TEST(gXULLog, LogLevel::Debug)) {
+ MOZ_LOG(gXULLog, LogLevel::Debug,
+ ("xul: creating <%s> from prototype",
+ NS_ConvertUTF16toUTF8(aPrototype->mNodeInfo->QualifiedName()).get()));
+ }
+
+ RefPtr<Element> result;
+
+ if (aPrototype->mNodeInfo->NamespaceEquals(kNameSpaceID_XUL)) {
+ // If it's a XUL element, it'll be lightweight until somebody
+ // monkeys with it.
+ rv = nsXULElement::Create(aPrototype, this, true, aIsRoot, getter_AddRefs(result));
+ if (NS_FAILED(rv)) return rv;
+ }
+ else {
+ // If it's not a XUL element, it's gonna be heavyweight no matter
+ // what. So we need to copy everything out of the prototype
+ // into the element. Get a nodeinfo from our nodeinfo manager
+ // for this node.
+ RefPtr<mozilla::dom::NodeInfo> newNodeInfo;
+ newNodeInfo = mNodeInfoManager->GetNodeInfo(aPrototype->mNodeInfo->NameAtom(),
+ aPrototype->mNodeInfo->GetPrefixAtom(),
+ aPrototype->mNodeInfo->NamespaceID(),
+ nsIDOMNode::ELEMENT_NODE);
+ if (!newNodeInfo) return NS_ERROR_OUT_OF_MEMORY;
+ RefPtr<mozilla::dom::NodeInfo> xtfNi = newNodeInfo;
+ rv = NS_NewElement(getter_AddRefs(result), newNodeInfo.forget(),
+ NOT_FROM_PARSER);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = AddAttributes(aPrototype, result);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ result.forget(aResult);
+
+ return NS_OK;
+}
+
+nsresult
+XULDocument::CreateOverlayElement(nsXULPrototypeElement* aPrototype,
+ Element** aResult)
+{
+ nsresult rv;
+
+ RefPtr<Element> element;
+ rv = CreateElementFromPrototype(aPrototype, getter_AddRefs(element), false);
+ if (NS_FAILED(rv)) return rv;
+
+ OverlayForwardReference* fwdref =
+ new OverlayForwardReference(this, element);
+
+ // transferring ownership to ya...
+ rv = AddForwardReference(fwdref);
+ if (NS_FAILED(rv)) return rv;
+
+ element.forget(aResult);
+ return NS_OK;
+}
+
+nsresult
+XULDocument::AddAttributes(nsXULPrototypeElement* aPrototype,
+ nsIContent* aElement)
+{
+ nsresult rv;
+
+ for (uint32_t i = 0; i < aPrototype->mNumAttributes; ++i) {
+ nsXULPrototypeAttribute* protoattr = &(aPrototype->mAttributes[i]);
+ nsAutoString valueStr;
+ protoattr->mValue.ToString(valueStr);
+
+ rv = aElement->SetAttr(protoattr->mName.NamespaceID(),
+ protoattr->mName.LocalName(),
+ protoattr->mName.GetPrefix(),
+ valueStr,
+ false);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+XULDocument::CheckTemplateBuilderHookup(nsIContent* aElement,
+ bool* aNeedsHookup)
+{
+ // See if the element already has a `database' attribute. If it
+ // does, then the template builder has already been created.
+ //
+ // XXX This approach will crash and burn (well, maybe not _that_
+ // bad) if aElement is not a XUL element.
+ //
+ // XXXvarga Do we still want to support non XUL content?
+ nsCOMPtr<nsIDOMXULElement> xulElement = do_QueryInterface(aElement);
+ if (xulElement) {
+ nsCOMPtr<nsIRDFCompositeDataSource> ds;
+ xulElement->GetDatabase(getter_AddRefs(ds));
+ if (ds) {
+ *aNeedsHookup = false;
+ return NS_OK;
+ }
+ }
+
+ // Check aElement for a 'datasources' attribute, if it has
+ // one a XUL template builder needs to be hooked up.
+ *aNeedsHookup = aElement->HasAttr(kNameSpaceID_None,
+ nsGkAtoms::datasources);
+ return NS_OK;
+}
+
+/* static */ nsresult
+XULDocument::CreateTemplateBuilder(nsIContent* aElement)
+{
+ // Check if need to construct a tree builder or content builder.
+ bool isTreeBuilder = false;
+
+ // return successful if the element is not is a document, as an inline
+ // script could have removed it
+ nsIDocument* document = aElement->GetUncomposedDoc();
+ NS_ENSURE_TRUE(document, NS_OK);
+
+ int32_t nameSpaceID;
+ nsIAtom* baseTag = document->BindingManager()->
+ ResolveTag(aElement, &nameSpaceID);
+
+ if ((nameSpaceID == kNameSpaceID_XUL) && (baseTag == nsGkAtoms::tree)) {
+ // By default, we build content for a tree and then we attach
+ // the tree content view. However, if the `dont-build-content'
+ // flag is set, then we we'll attach a tree builder which
+ // directly implements the tree view.
+
+ nsAutoString flags;
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::flags, flags);
+ if (flags.Find(NS_LITERAL_STRING("dont-build-content")) >= 0) {
+ isTreeBuilder = true;
+ }
+ }
+
+ if (isTreeBuilder) {
+ // Create and initialize a tree builder.
+ nsCOMPtr<nsIXULTemplateBuilder> builder =
+ do_CreateInstance("@mozilla.org/xul/xul-tree-builder;1");
+
+ if (! builder)
+ return NS_ERROR_FAILURE;
+
+ builder->Init(aElement);
+
+ // Create a <treechildren> if one isn't there already.
+ // XXXvarga what about attributes?
+ nsCOMPtr<nsIContent> bodyContent;
+ nsXULContentUtils::FindChildByTag(aElement, kNameSpaceID_XUL,
+ nsGkAtoms::treechildren,
+ getter_AddRefs(bodyContent));
+
+ if (! bodyContent) {
+ bodyContent =
+ document->CreateElem(nsDependentAtomString(nsGkAtoms::treechildren),
+ nullptr, kNameSpaceID_XUL);
+
+ aElement->AppendChildTo(bodyContent, false);
+ }
+ }
+ else {
+ // Create and initialize a content builder.
+ nsCOMPtr<nsIXULTemplateBuilder> builder
+ = do_CreateInstance("@mozilla.org/xul/xul-template-builder;1");
+
+ if (! builder)
+ return NS_ERROR_FAILURE;
+
+ builder->Init(aElement);
+ builder->CreateContents(aElement, false);
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+XULDocument::AddPrototypeSheets()
+{
+ nsresult rv;
+
+ const nsCOMArray<nsIURI>& sheets = mCurrentPrototype->GetStyleSheetReferences();
+
+ for (int32_t i = 0; i < sheets.Count(); i++) {
+ nsCOMPtr<nsIURI> uri = sheets[i];
+
+ RefPtr<StyleSheet> incompleteSheet;
+ rv = CSSLoader()->LoadSheet(uri,
+ mCurrentPrototype->DocumentPrincipal(),
+ EmptyCString(), this,
+ &incompleteSheet);
+
+ // XXXldb We need to prevent bogus sheets from being held in the
+ // prototype's list, but until then, don't propagate the failure
+ // from LoadSheet (and thus exit the loop).
+ if (NS_SUCCEEDED(rv)) {
+ ++mPendingSheets;
+ if (!mOverlaySheets.AppendElement(incompleteSheet)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+
+//----------------------------------------------------------------------
+//
+// XULDocument::OverlayForwardReference
+//
+
+nsForwardReference::Result
+XULDocument::OverlayForwardReference::Resolve()
+{
+ // Resolve a forward reference from an overlay element; attempt to
+ // hook it up into the main document.
+ nsresult rv;
+ nsCOMPtr<nsIContent> target;
+
+ nsIPresShell *shell = mDocument->GetShell();
+ bool notify = shell && shell->DidInitialize();
+
+ nsAutoString id;
+ mOverlay->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
+ if (id.IsEmpty()) {
+ // mOverlay is a direct child of <overlay> and has no id.
+ // Insert it under the root element in the base document.
+ Element* root = mDocument->GetRootElement();
+ if (!root) {
+ return eResolve_Error;
+ }
+
+ rv = mDocument->InsertElement(root, mOverlay, notify);
+ if (NS_FAILED(rv)) return eResolve_Error;
+
+ target = mOverlay;
+ }
+ else {
+ // The hook-up element has an id, try to match it with an element
+ // with the same id in the base document.
+ target = mDocument->GetElementById(id);
+
+ // If we can't find the element in the document, defer the hookup
+ // until later.
+ if (!target)
+ return eResolve_Later;
+
+ rv = Merge(target, mOverlay, notify);
+ if (NS_FAILED(rv)) return eResolve_Error;
+ }
+
+ // Check if 'target' is still in our document --- it might not be!
+ if (!notify && target->GetUncomposedDoc() == mDocument) {
+ // Add child and any descendants to the element map
+ // XXX this is bogus, the content in 'target' might already be
+ // in the document
+ rv = mDocument->AddSubtreeToDocument(target);
+ if (NS_FAILED(rv)) return eResolve_Error;
+ }
+
+ if (MOZ_LOG_TEST(gXULLog, LogLevel::Debug)) {
+ nsAutoCString idC;
+ idC.AssignWithConversion(id);
+ MOZ_LOG(gXULLog, LogLevel::Debug,
+ ("xul: overlay resolved '%s'",
+ idC.get()));
+ }
+
+ mResolved = true;
+ return eResolve_Succeeded;
+}
+
+
+
+nsresult
+XULDocument::OverlayForwardReference::Merge(nsIContent* aTargetNode,
+ nsIContent* aOverlayNode,
+ bool aNotify)
+{
+ // This function is given:
+ // aTargetNode: the node in the document whose 'id' attribute
+ // matches a toplevel node in our overlay.
+ // aOverlayNode: the node in the overlay document that matches
+ // a node in the actual document.
+ // aNotify: whether or not content manipulation methods should
+ // use the aNotify parameter. After the initial
+ // reflow (i.e. in the dynamic overlay merge case),
+ // we want all the content manipulation methods we
+ // call to notify so that frames are constructed
+ // etc. Otherwise do not, since that's during initial
+ // document construction before StartLayout has been
+ // called which will do everything for us.
+ //
+ // This function merges the tree from the overlay into the tree in
+ // the document, overwriting attributes and appending child content
+ // nodes appropriately. (See XUL overlay reference for details)
+
+ nsresult rv;
+
+ // Merge attributes from the overlay content node to that of the
+ // actual document.
+ uint32_t i;
+ const nsAttrName* name;
+ for (i = 0; (name = aOverlayNode->GetAttrNameAt(i)); ++i) {
+ // We don't want to swap IDs, they should be the same.
+ if (name->Equals(nsGkAtoms::id))
+ continue;
+
+ // In certain cases merging command or observes is unsafe, so don't.
+ if (!aNotify) {
+ if (aTargetNode->NodeInfo()->Equals(nsGkAtoms::observes,
+ kNameSpaceID_XUL))
+ continue;
+
+ if (name->Equals(nsGkAtoms::observes) &&
+ aTargetNode->HasAttr(kNameSpaceID_None, nsGkAtoms::observes))
+ continue;
+
+ if (name->Equals(nsGkAtoms::command) &&
+ aTargetNode->HasAttr(kNameSpaceID_None, nsGkAtoms::command) &&
+ !aTargetNode->NodeInfo()->Equals(nsGkAtoms::key,
+ kNameSpaceID_XUL) &&
+ !aTargetNode->NodeInfo()->Equals(nsGkAtoms::menuitem,
+ kNameSpaceID_XUL))
+ continue;
+ }
+
+ int32_t nameSpaceID = name->NamespaceID();
+ nsIAtom* attr = name->LocalName();
+ nsIAtom* prefix = name->GetPrefix();
+
+ nsAutoString value;
+ aOverlayNode->GetAttr(nameSpaceID, attr, value);
+
+ // Element in the overlay has the 'removeelement' attribute set
+ // so remove it from the actual document.
+ if (attr == nsGkAtoms::removeelement &&
+ value.EqualsLiteral("true")) {
+
+ nsCOMPtr<nsINode> parent = aTargetNode->GetParentNode();
+ if (!parent) return NS_ERROR_FAILURE;
+ rv = RemoveElement(parent, aTargetNode);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+ }
+
+ rv = aTargetNode->SetAttr(nameSpaceID, attr, prefix, value, aNotify);
+ if (!NS_FAILED(rv) && !aNotify)
+ rv = mDocument->BroadcastAttributeChangeFromOverlay(aTargetNode,
+ nameSpaceID,
+ attr, prefix,
+ value);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+
+ // Walk our child nodes, looking for elements that have the 'id'
+ // attribute set. If we find any, we must do a parent check in the
+ // actual document to ensure that the structure matches that of
+ // the actual document. If it does, we can call ourselves and attempt
+ // to merge inside that subtree. If not, we just append the tree to
+ // the parent like any other.
+
+ uint32_t childCount = aOverlayNode->GetChildCount();
+
+ // This must be a strong reference since it will be the only
+ // reference to a content object during part of this loop.
+ nsCOMPtr<nsIContent> currContent;
+
+ for (i = 0; i < childCount; ++i) {
+ currContent = aOverlayNode->GetFirstChild();
+
+ nsIAtom *idAtom = currContent->GetID();
+
+ nsIContent *elementInDocument = nullptr;
+ if (idAtom) {
+ nsDependentAtomString id(idAtom);
+
+ if (!id.IsEmpty()) {
+ nsIDocument *doc = aTargetNode->GetUncomposedDoc();
+ //XXXsmaug should we use ShadowRoot::GetElementById()
+ // if doc is null?
+ if (!doc) return NS_ERROR_FAILURE;
+
+ elementInDocument = doc->GetElementById(id);
+ }
+ }
+
+ // The item has an 'id' attribute set, and we need to check with
+ // the actual document to see if an item with this id exists at
+ // this locale. If so, we want to merge the subtree under that
+ // node. Otherwise, we just do an append as if the element had
+ // no id attribute.
+ if (elementInDocument) {
+ // Given two parents, aTargetNode and aOverlayNode, we want
+ // to call merge on currContent if we find an associated
+ // node in the document with the same id as currContent that
+ // also has aTargetNode as its parent.
+
+ nsIContent *elementParent = elementInDocument->GetParent();
+
+ nsIAtom *parentID = elementParent->GetID();
+ if (parentID &&
+ aTargetNode->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id,
+ nsDependentAtomString(parentID),
+ eCaseMatters)) {
+ // The element matches. "Go Deep!"
+ rv = Merge(elementInDocument, currContent, aNotify);
+ if (NS_FAILED(rv)) return rv;
+ aOverlayNode->RemoveChildAt(0, false);
+
+ continue;
+ }
+ }
+
+ aOverlayNode->RemoveChildAt(0, false);
+
+ rv = InsertElement(aTargetNode, currContent, aNotify);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+
+
+XULDocument::OverlayForwardReference::~OverlayForwardReference()
+{
+ if (MOZ_LOG_TEST(gXULLog, LogLevel::Warning) && !mResolved) {
+ nsAutoString id;
+ mOverlay->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
+
+ nsAutoCString idC;
+ idC.AssignWithConversion(id);
+
+ nsIURI *protoURI = mDocument->mCurrentPrototype->GetURI();
+
+ nsCOMPtr<nsIURI> docURI;
+ mDocument->mChannel->GetOriginalURI(getter_AddRefs(docURI));
+
+ MOZ_LOG(gXULLog, LogLevel::Warning,
+ ("xul: %s overlay failed to resolve '%s' in %s",
+ protoURI->GetSpecOrDefault().get(), idC.get(),
+ docURI ? docURI->GetSpecOrDefault().get() : ""));
+ }
+}
+
+
+//----------------------------------------------------------------------
+//
+// XULDocument::BroadcasterHookup
+//
+
+nsForwardReference::Result
+XULDocument::BroadcasterHookup::Resolve()
+{
+ nsresult rv;
+
+ bool listener;
+ rv = mDocument->CheckBroadcasterHookup(mObservesElement, &listener, &mResolved);
+ if (NS_FAILED(rv)) return eResolve_Error;
+
+ return mResolved ? eResolve_Succeeded : eResolve_Later;
+}
+
+
+XULDocument::BroadcasterHookup::~BroadcasterHookup()
+{
+ if (MOZ_LOG_TEST(gXULLog, LogLevel::Warning) && !mResolved) {
+ // Tell the world we failed
+
+ nsAutoString broadcasterID;
+ nsAutoString attribute;
+
+ if (mObservesElement->IsXULElement(nsGkAtoms::observes)) {
+ mObservesElement->GetAttr(kNameSpaceID_None, nsGkAtoms::element, broadcasterID);
+ mObservesElement->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, attribute);
+ }
+ else {
+ mObservesElement->GetAttr(kNameSpaceID_None, nsGkAtoms::observes, broadcasterID);
+ attribute.Assign('*');
+ }
+
+ nsAutoCString attributeC,broadcasteridC;
+ attributeC.AssignWithConversion(attribute);
+ broadcasteridC.AssignWithConversion(broadcasterID);
+ MOZ_LOG(gXULLog, LogLevel::Warning,
+ ("xul: broadcaster hookup failed <%s attribute='%s'> to %s",
+ nsAtomCString(mObservesElement->NodeInfo()->NameAtom()).get(),
+ attributeC.get(),
+ broadcasteridC.get()));
+ }
+}
+
+
+//----------------------------------------------------------------------
+//
+// XULDocument::TemplateBuilderHookup
+//
+
+nsForwardReference::Result
+XULDocument::TemplateBuilderHookup::Resolve()
+{
+ bool needsHookup;
+ nsresult rv = CheckTemplateBuilderHookup(mElement, &needsHookup);
+ if (NS_FAILED(rv))
+ return eResolve_Error;
+
+ if (needsHookup) {
+ rv = CreateTemplateBuilder(mElement);
+ if (NS_FAILED(rv))
+ return eResolve_Error;
+ }
+
+ return eResolve_Succeeded;
+}
+
+
+//----------------------------------------------------------------------
+
+nsresult
+XULDocument::BroadcastAttributeChangeFromOverlay(nsIContent* aNode,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ nsIAtom* aPrefix,
+ const nsAString& aValue)
+{
+ nsresult rv = NS_OK;
+
+ if (!mBroadcasterMap || !CanBroadcast(aNameSpaceID, aAttribute))
+ return rv;
+
+ if (!aNode->IsElement())
+ return rv;
+
+ auto entry = static_cast<BroadcasterMapEntry*>
+ (mBroadcasterMap->Search(aNode->AsElement()));
+ if (!entry)
+ return rv;
+
+ // We've got listeners: push the value.
+ for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
+ BroadcastListener* bl = entry->mListeners[i];
+
+ if ((bl->mAttribute != aAttribute) &&
+ (bl->mAttribute != nsGkAtoms::_asterisk))
+ continue;
+
+ nsCOMPtr<nsIContent> l = do_QueryReferent(bl->mListener);
+ if (l) {
+ rv = l->SetAttr(aNameSpaceID, aAttribute,
+ aPrefix, aValue, false);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+ return rv;
+}
+
+nsresult
+XULDocument::FindBroadcaster(Element* aElement,
+ Element** aListener,
+ nsString& aBroadcasterID,
+ nsString& aAttribute,
+ Element** aBroadcaster)
+{
+ mozilla::dom::NodeInfo *ni = aElement->NodeInfo();
+ *aListener = nullptr;
+ *aBroadcaster = nullptr;
+
+ if (ni->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) {
+ // It's an <observes> element, which means that the actual
+ // listener is the _parent_ node. This element should have an
+ // 'element' attribute that specifies the ID of the
+ // broadcaster element, and an 'attribute' element, which
+ // specifies the name of the attribute to observe.
+ nsIContent* parent = aElement->GetParent();
+ if (!parent) {
+ // <observes> is the root element
+ return NS_FINDBROADCASTER_NOT_FOUND;
+ }
+
+ // If we're still parented by an 'overlay' tag, then we haven't
+ // made it into the real document yet. Defer hookup.
+ if (parent->NodeInfo()->Equals(nsGkAtoms::overlay,
+ kNameSpaceID_XUL)) {
+ return NS_FINDBROADCASTER_AWAIT_OVERLAYS;
+ }
+
+ *aListener = parent->IsElement() ? parent->AsElement() : nullptr;
+ NS_IF_ADDREF(*aListener);
+
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::element, aBroadcasterID);
+ if (aBroadcasterID.IsEmpty()) {
+ return NS_FINDBROADCASTER_NOT_FOUND;
+ }
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, aAttribute);
+ }
+ else {
+ // It's a generic element, which means that we'll use the
+ // value of the 'observes' attribute to determine the ID of
+ // the broadcaster element, and we'll watch _all_ of its
+ // values.
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::observes, aBroadcasterID);
+
+ // Bail if there's no aBroadcasterID
+ if (aBroadcasterID.IsEmpty()) {
+ // Try the command attribute next.
+ aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::command, aBroadcasterID);
+ if (!aBroadcasterID.IsEmpty()) {
+ // We've got something in the command attribute. We
+ // only treat this as a normal broadcaster if we are
+ // not a menuitem or a key.
+
+ if (ni->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL) ||
+ ni->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) {
+ return NS_FINDBROADCASTER_NOT_FOUND;
+ }
+ }
+ else {
+ return NS_FINDBROADCASTER_NOT_FOUND;
+ }
+ }
+
+ *aListener = aElement;
+ NS_ADDREF(*aListener);
+
+ aAttribute.Assign('*');
+ }
+
+ // Make sure we got a valid listener.
+ NS_ENSURE_TRUE(*aListener, NS_ERROR_UNEXPECTED);
+
+ // Try to find the broadcaster element in the document.
+ *aBroadcaster = GetElementById(aBroadcasterID);
+
+ // If we can't find the broadcaster, then we'll need to defer the
+ // hookup. We may need to resolve some of the other overlays
+ // first.
+ if (! *aBroadcaster) {
+ return NS_FINDBROADCASTER_AWAIT_OVERLAYS;
+ }
+
+ NS_ADDREF(*aBroadcaster);
+
+ return NS_FINDBROADCASTER_FOUND;
+}
+
+nsresult
+XULDocument::CheckBroadcasterHookup(Element* aElement,
+ bool* aNeedsHookup,
+ bool* aDidResolve)
+{
+ // Resolve a broadcaster hookup. Look at the element that we're
+ // trying to resolve: it could be an '<observes>' element, or just
+ // a vanilla element with an 'observes' attribute on it.
+ nsresult rv;
+
+ *aDidResolve = false;
+
+ nsCOMPtr<Element> listener;
+ nsAutoString broadcasterID;
+ nsAutoString attribute;
+ nsCOMPtr<Element> broadcaster;
+
+ rv = FindBroadcaster(aElement, getter_AddRefs(listener),
+ broadcasterID, attribute, getter_AddRefs(broadcaster));
+ switch (rv) {
+ case NS_FINDBROADCASTER_NOT_FOUND:
+ *aNeedsHookup = false;
+ return NS_OK;
+ case NS_FINDBROADCASTER_AWAIT_OVERLAYS:
+ *aNeedsHookup = true;
+ return NS_OK;
+ case NS_FINDBROADCASTER_FOUND:
+ break;
+ default:
+ return rv;
+ }
+
+ NS_ENSURE_ARG(broadcaster && listener);
+ ErrorResult domRv;
+ AddBroadcastListenerFor(*broadcaster, *listener, attribute, domRv);
+ if (domRv.Failed()) {
+ return domRv.StealNSResult();
+ }
+
+ // Tell the world we succeeded
+ if (MOZ_LOG_TEST(gXULLog, LogLevel::Debug)) {
+ nsCOMPtr<nsIContent> content =
+ do_QueryInterface(listener);
+
+ NS_ASSERTION(content != nullptr, "not an nsIContent");
+ if (! content)
+ return rv;
+
+ nsAutoCString attributeC,broadcasteridC;
+ attributeC.AssignWithConversion(attribute);
+ broadcasteridC.AssignWithConversion(broadcasterID);
+ MOZ_LOG(gXULLog, LogLevel::Debug,
+ ("xul: broadcaster hookup <%s attribute='%s'> to %s",
+ nsAtomCString(content->NodeInfo()->NameAtom()).get(),
+ attributeC.get(),
+ broadcasteridC.get()));
+ }
+
+ *aNeedsHookup = false;
+ *aDidResolve = true;
+ return NS_OK;
+}
+
+nsresult
+XULDocument::InsertElement(nsINode* aParent, nsIContent* aChild,
+ bool aNotify)
+{
+ // Insert aChild appropriately into aParent, accounting for a
+ // 'pos' attribute set on aChild.
+
+ nsAutoString posStr;
+ bool wasInserted = false;
+
+ // insert after an element of a given id
+ aChild->GetAttr(kNameSpaceID_None, nsGkAtoms::insertafter, posStr);
+ bool isInsertAfter = true;
+
+ if (posStr.IsEmpty()) {
+ aChild->GetAttr(kNameSpaceID_None, nsGkAtoms::insertbefore, posStr);
+ isInsertAfter = false;
+ }
+
+ if (!posStr.IsEmpty()) {
+ nsIDocument *document = aParent->OwnerDoc();
+
+ nsIContent *content = nullptr;
+
+ char* str = ToNewCString(posStr);
+ char* rest;
+ char* token = nsCRT::strtok(str, ", ", &rest);
+
+ while (token) {
+ content = document->GetElementById(NS_ConvertASCIItoUTF16(token));
+ if (content)
+ break;
+
+ token = nsCRT::strtok(rest, ", ", &rest);
+ }
+ free(str);
+
+ if (content) {
+ int32_t pos = aParent->IndexOf(content);
+
+ if (pos != -1) {
+ pos = isInsertAfter ? pos + 1 : pos;
+ nsresult rv = aParent->InsertChildAt(aChild, pos, aNotify);
+ if (NS_FAILED(rv))
+ return rv;
+
+ wasInserted = true;
+ }
+ }
+ }
+
+ if (!wasInserted) {
+
+ aChild->GetAttr(kNameSpaceID_None, nsGkAtoms::position, posStr);
+ if (!posStr.IsEmpty()) {
+ nsresult rv;
+ // Positions are one-indexed.
+ int32_t pos = posStr.ToInteger(&rv);
+ // Note: if the insertion index (which is |pos - 1|) would be less
+ // than 0 or greater than the number of children aParent has, then
+ // don't insert, since the position is bogus. Just skip on to
+ // appending.
+ if (NS_SUCCEEDED(rv) && pos > 0 &&
+ uint32_t(pos - 1) <= aParent->GetChildCount()) {
+ rv = aParent->InsertChildAt(aChild, pos - 1, aNotify);
+ if (NS_SUCCEEDED(rv))
+ wasInserted = true;
+ // If the insertion fails, then we should still
+ // attempt an append. Thus, rather than returning rv
+ // immediately, we fall through to the final
+ // "catch-all" case that just does an AppendChildTo.
+ }
+ }
+ }
+
+ if (!wasInserted) {
+ return aParent->AppendChildTo(aChild, aNotify);
+ }
+ return NS_OK;
+}
+
+nsresult
+XULDocument::RemoveElement(nsINode* aParent, nsINode* aChild)
+{
+ int32_t nodeOffset = aParent->IndexOf(aChild);
+
+ aParent->RemoveChildAt(nodeOffset, true);
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+//
+// CachedChromeStreamListener
+//
+
+XULDocument::CachedChromeStreamListener::CachedChromeStreamListener(XULDocument* aDocument, bool aProtoLoaded)
+ : mDocument(aDocument),
+ mProtoLoaded(aProtoLoaded)
+{
+}
+
+
+XULDocument::CachedChromeStreamListener::~CachedChromeStreamListener()
+{
+}
+
+
+NS_IMPL_ISUPPORTS(XULDocument::CachedChromeStreamListener,
+ nsIRequestObserver, nsIStreamListener)
+
+NS_IMETHODIMP
+XULDocument::CachedChromeStreamListener::OnStartRequest(nsIRequest *request,
+ nsISupports* acontext)
+{
+ return NS_ERROR_PARSED_DATA_CACHED;
+}
+
+
+NS_IMETHODIMP
+XULDocument::CachedChromeStreamListener::OnStopRequest(nsIRequest *request,
+ nsISupports* aContext,
+ nsresult aStatus)
+{
+ if (! mProtoLoaded)
+ return NS_OK;
+
+ return mDocument->OnPrototypeLoadDone(true);
+}
+
+
+NS_IMETHODIMP
+XULDocument::CachedChromeStreamListener::OnDataAvailable(nsIRequest *request,
+ nsISupports* aContext,
+ nsIInputStream* aInStr,
+ uint64_t aSourceOffset,
+ uint32_t aCount)
+{
+ NS_NOTREACHED("CachedChromeStream doesn't receive data");
+ return NS_ERROR_UNEXPECTED;
+}
+
+//----------------------------------------------------------------------
+//
+// ParserObserver
+//
+
+XULDocument::ParserObserver::ParserObserver(XULDocument* aDocument,
+ nsXULPrototypeDocument* aPrototype)
+ : mDocument(aDocument), mPrototype(aPrototype)
+{
+}
+
+XULDocument::ParserObserver::~ParserObserver()
+{
+}
+
+NS_IMPL_ISUPPORTS(XULDocument::ParserObserver, nsIRequestObserver)
+
+NS_IMETHODIMP
+XULDocument::ParserObserver::OnStartRequest(nsIRequest *request,
+ nsISupports* aContext)
+{
+ // Guard against buggy channels calling OnStartRequest multiple times.
+ if (mPrototype) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
+ if (channel && secMan) {
+ nsCOMPtr<nsIPrincipal> principal;
+ secMan->GetChannelResultPrincipal(channel, getter_AddRefs(principal));
+
+ // Failure there is ok -- it'll just set a (safe) null principal
+ mPrototype->SetDocumentPrincipal(principal);
+ }
+
+ // Make sure to avoid cycles
+ mPrototype = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULDocument::ParserObserver::OnStopRequest(nsIRequest *request,
+ nsISupports* aContext,
+ nsresult aStatus)
+{
+ nsresult rv = NS_OK;
+
+ if (NS_FAILED(aStatus)) {
+ // If an overlay load fails, we need to nudge the prototype
+ // walk along.
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
+ if (aChannel) {
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetOriginalURI(getter_AddRefs(uri));
+ if (uri) {
+ mDocument->ReportMissingOverlay(uri);
+ }
+ }
+
+ rv = mDocument->ResumeWalk();
+ }
+
+ // Drop the reference to the document to break cycle between the
+ // document, the parser, the content sink, and the parser
+ // observer.
+ mDocument = nullptr;
+
+ return rv;
+}
+
+already_AddRefed<nsPIWindowRoot>
+XULDocument::GetWindowRoot()
+{
+ if (!mDocumentContainer) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> piWin = mDocumentContainer->GetWindow();
+ return piWin ? piWin->GetTopWindowRoot() : nullptr;
+}
+
+bool
+XULDocument::IsDocumentRightToLeft()
+{
+ // setting the localedir attribute on the root element forces a
+ // specific direction for the document.
+ Element* element = GetRootElement();
+ if (element) {
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::ltr, &nsGkAtoms::rtl, nullptr};
+ switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::localedir,
+ strings, eCaseMatters)) {
+ case 0: return false;
+ case 1: return true;
+ default: break; // otherwise, not a valid value, so fall through
+ }
+ }
+
+ // otherwise, get the locale from the chrome registry and
+ // look up the intl.uidirection.<locale> preference
+ nsCOMPtr<nsIXULChromeRegistry> reg =
+ mozilla::services::GetXULChromeRegistryService();
+ if (!reg)
+ return false;
+
+ nsAutoCString package;
+ bool isChrome;
+ if (NS_SUCCEEDED(mDocumentURI->SchemeIs("chrome", &isChrome)) &&
+ isChrome) {
+ mDocumentURI->GetHostPort(package);
+ }
+ else {
+ // use the 'global' package for about and resource uris.
+ // otherwise, just default to left-to-right.
+ bool isAbout, isResource;
+ if (NS_SUCCEEDED(mDocumentURI->SchemeIs("about", &isAbout)) &&
+ isAbout) {
+ package.AssignLiteral("global");
+ }
+ else if (NS_SUCCEEDED(mDocumentURI->SchemeIs("resource", &isResource)) &&
+ isResource) {
+ package.AssignLiteral("global");
+ }
+ else {
+ return false;
+ }
+ }
+
+ bool isRTL = false;
+ reg->IsLocaleRTL(package, &isRTL);
+ return isRTL;
+}
+
+void
+XULDocument::ResetDocumentDirection()
+{
+ DocumentStatesChanged(NS_DOCUMENT_STATE_RTL_LOCALE);
+}
+
+void
+XULDocument::DirectionChanged(const char* aPrefName, void* aData)
+{
+ // Reset the direction and restyle the document if necessary.
+ XULDocument* doc = (XULDocument *)aData;
+ if (doc) {
+ doc->ResetDocumentDirection();
+ }
+}
+
+int
+XULDocument::GetDocumentLWTheme()
+{
+ if (mDocLWTheme == Doc_Theme_Uninitialized) {
+ mDocLWTheme = Doc_Theme_None; // No lightweight theme by default
+
+ Element* element = GetRootElement();
+ nsAutoString hasLWTheme;
+ if (element &&
+ element->GetAttr(kNameSpaceID_None, nsGkAtoms::lwtheme, hasLWTheme) &&
+ !(hasLWTheme.IsEmpty()) &&
+ hasLWTheme.EqualsLiteral("true")) {
+ mDocLWTheme = Doc_Theme_Neutral;
+ nsAutoString lwTheme;
+ element->GetAttr(kNameSpaceID_None, nsGkAtoms::lwthemetextcolor, lwTheme);
+ if (!(lwTheme.IsEmpty())) {
+ if (lwTheme.EqualsLiteral("dark"))
+ mDocLWTheme = Doc_Theme_Dark;
+ else if (lwTheme.EqualsLiteral("bright"))
+ mDocLWTheme = Doc_Theme_Bright;
+ }
+ }
+ }
+ return mDocLWTheme;
+}
+
+NS_IMETHODIMP
+XULDocument::GetBoxObjectFor(nsIDOMElement* aElement, nsIBoxObject** aResult)
+{
+ ErrorResult rv;
+ nsCOMPtr<Element> el = do_QueryInterface(aElement);
+ *aResult = GetBoxObjectFor(el, rv).take();
+ return rv.StealNSResult();
+}
+
+JSObject*
+XULDocument::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return XULDocumentBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/xul/XULDocument.h b/dom/xul/XULDocument.h
new file mode 100644
index 0000000000..a2f82bb896
--- /dev/null
+++ b/dom/xul/XULDocument.h
@@ -0,0 +1,791 @@
+/* -*- 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 mozilla_dom_XULDocument_h
+#define mozilla_dom_XULDocument_h
+
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsXULPrototypeDocument.h"
+#include "nsXULPrototypeCache.h"
+#include "nsTArray.h"
+
+#include "mozilla/dom/XMLDocument.h"
+#include "mozilla/StyleSheet.h"
+#include "nsForwardReference.h"
+#include "nsIContent.h"
+#include "nsIDOMXULCommandDispatcher.h"
+#include "nsIDOMXULDocument.h"
+#include "nsCOMArray.h"
+#include "nsIURI.h"
+#include "nsIXULDocument.h"
+#include "nsScriptLoader.h"
+#include "nsIStreamListener.h"
+#include "nsIStreamLoader.h"
+#include "nsICSSLoaderObserver.h"
+#include "nsIXULStore.h"
+
+#include "mozilla/Attributes.h"
+
+#include "js/TracingAPI.h"
+#include "js/TypeDecls.h"
+
+class nsIRDFResource;
+class nsIRDFService;
+class nsPIWindowRoot;
+#if 0 // XXXbe save me, scc (need NSCAP_FORWARD_DECL(nsXULPrototypeScript))
+class nsIObjectInputStream;
+class nsIObjectOutputStream;
+#else
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsXULElement.h"
+#endif
+#include "nsURIHashKey.h"
+#include "nsInterfaceHashtable.h"
+
+class nsRefMapEntry : public nsStringHashKey
+{
+public:
+ explicit nsRefMapEntry(const nsAString& aKey) :
+ nsStringHashKey(&aKey)
+ {
+ }
+ explicit nsRefMapEntry(const nsAString* aKey) :
+ nsStringHashKey(aKey)
+ {
+ }
+ nsRefMapEntry(const nsRefMapEntry& aOther) :
+ nsStringHashKey(&aOther.GetKey())
+ {
+ NS_ERROR("Should never be called");
+ }
+
+ mozilla::dom::Element* GetFirstElement();
+ void AppendAll(nsCOMArray<nsIContent>* aElements);
+ /**
+ * @return true if aElement was added, false if we failed due to OOM
+ */
+ bool AddElement(mozilla::dom::Element* aElement);
+ /**
+ * @return true if aElement was removed and it was the last content for
+ * this ref, so this entry should be removed from the map
+ */
+ bool RemoveElement(mozilla::dom::Element* aElement);
+
+private:
+ nsTArray<mozilla::dom::Element*> mRefContentList;
+};
+
+/**
+ * The XUL document class
+ */
+
+namespace mozilla {
+namespace dom {
+
+class XULDocument final : public XMLDocument,
+ public nsIXULDocument,
+ public nsIDOMXULDocument,
+ public nsIStreamLoaderObserver,
+ public nsICSSLoaderObserver,
+ public nsIOffThreadScriptReceiver
+{
+public:
+ XULDocument();
+
+ // nsISupports interface
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+ // nsIDocument interface
+ virtual void Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) override;
+ virtual void ResetToURI(nsIURI *aURI, nsILoadGroup* aLoadGroup,
+ nsIPrincipal* aPrincipal) override;
+
+ virtual nsresult StartDocumentLoad(const char* aCommand,
+ nsIChannel *channel,
+ nsILoadGroup* aLoadGroup,
+ nsISupports* aContainer,
+ nsIStreamListener **aDocListener,
+ bool aReset = true,
+ nsIContentSink* aSink = nullptr) override;
+
+ virtual void SetContentType(const nsAString& aContentType) override;
+
+ virtual void EndLoad() override;
+
+ // nsIMutationObserver interface
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE
+
+ // nsIXULDocument interface
+ virtual void GetElementsForID(const nsAString& aID,
+ nsCOMArray<nsIContent>& aElements) override;
+
+ NS_IMETHOD AddSubtreeToDocument(nsIContent* aContent) override;
+ NS_IMETHOD RemoveSubtreeFromDocument(nsIContent* aContent) override;
+ NS_IMETHOD SetTemplateBuilderFor(nsIContent* aContent,
+ nsIXULTemplateBuilder* aBuilder) override;
+ NS_IMETHOD GetTemplateBuilderFor(nsIContent* aContent,
+ nsIXULTemplateBuilder** aResult) override;
+ NS_IMETHOD OnPrototypeLoadDone(bool aResumeWalk) override;
+ bool OnDocumentParserError() override;
+
+ // nsINode interface overrides
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+
+ // nsIDOMNode interface
+ NS_FORWARD_NSIDOMNODE_TO_NSINODE
+
+ // nsIDOMDocument interface
+ using nsDocument::CreateElement;
+ using nsDocument::CreateElementNS;
+ NS_FORWARD_NSIDOMDOCUMENT(XMLDocument::)
+ // And explicitly import the things from nsDocument that we just shadowed
+ using nsDocument::GetImplementation;
+ using nsDocument::GetTitle;
+ using nsDocument::SetTitle;
+ using nsDocument::GetLastStyleSheetSet;
+ using nsDocument::MozSetImageElement;
+ using nsDocument::GetMozFullScreenElement;
+ using nsIDocument::GetLocation;
+
+ // nsDocument interface overrides
+ virtual Element* GetElementById(const nsAString & elementId) override;
+
+ // nsIDOMXULDocument interface
+ NS_DECL_NSIDOMXULDOCUMENT
+
+ // nsICSSLoaderObserver
+ NS_IMETHOD StyleSheetLoaded(mozilla::StyleSheet* aSheet,
+ bool aWasAlternate,
+ nsresult aStatus) override;
+
+ virtual void EndUpdate(nsUpdateType aUpdateType) override;
+
+ virtual bool IsDocumentRightToLeft() override;
+
+ virtual void ResetDocumentDirection() override;
+
+ virtual int GetDocumentLWTheme() override;
+
+ virtual void ResetDocumentLWTheme() override { mDocLWTheme = Doc_Theme_Uninitialized; }
+
+ NS_IMETHOD OnScriptCompileComplete(JSScript* aScript, nsresult aStatus) override;
+
+ static bool
+ MatchAttribute(nsIContent* aContent,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttrName,
+ void* aData);
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULDocument, XMLDocument)
+
+ void TraceProtos(JSTracer* aTrc, uint32_t aGCNumber);
+
+ // WebIDL API
+ already_AddRefed<nsINode> GetPopupNode();
+ void SetPopupNode(nsINode* aNode);
+ already_AddRefed<nsINode> GetPopupRangeParent(ErrorResult& aRv);
+ int32_t GetPopupRangeOffset(ErrorResult& aRv);
+ already_AddRefed<nsINode> GetTooltipNode();
+ void SetTooltipNode(nsINode* aNode) { /* do nothing */ }
+ nsIDOMXULCommandDispatcher* GetCommandDispatcher() const
+ {
+ return mCommandDispatcher;
+ }
+ int32_t GetWidth(ErrorResult& aRv);
+ int32_t GetHeight(ErrorResult& aRv);
+ already_AddRefed<nsINodeList>
+ GetElementsByAttribute(const nsAString& aAttribute,
+ const nsAString& aValue);
+ already_AddRefed<nsINodeList>
+ GetElementsByAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aAttribute,
+ const nsAString& aValue,
+ ErrorResult& aRv);
+ void AddBroadcastListenerFor(Element& aBroadcaster, Element& aListener,
+ const nsAString& aAttr, ErrorResult& aRv);
+ void RemoveBroadcastListenerFor(Element& aBroadcaster, Element& aListener,
+ const nsAString& aAttr);
+ void Persist(const nsAString& aId, const nsAString& aAttr, ErrorResult& aRv)
+ {
+ aRv = Persist(aId, aAttr);
+ }
+ using nsDocument::GetBoxObjectFor;
+ void LoadOverlay(const nsAString& aURL, nsIObserver* aObserver,
+ ErrorResult& aRv)
+ {
+ aRv = LoadOverlay(aURL, aObserver);
+ }
+
+protected:
+ virtual ~XULDocument();
+
+ // Implementation methods
+ friend nsresult
+ (::NS_NewXULDocument(nsIXULDocument** aResult));
+
+ nsresult Init(void) override;
+ nsresult StartLayout(void);
+
+ nsresult
+ AddElementToRefMap(Element* aElement);
+ void
+ RemoveElementFromRefMap(Element* aElement);
+
+ nsresult GetViewportSize(int32_t* aWidth, int32_t* aHeight);
+
+ nsresult PrepareToLoad(nsISupports* aContainer,
+ const char* aCommand,
+ nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ nsIParser** aResult);
+
+ nsresult
+ PrepareToLoadPrototype(nsIURI* aURI,
+ const char* aCommand,
+ nsIPrincipal* aDocumentPrincipal,
+ nsIParser** aResult);
+
+ nsresult
+ LoadOverlayInternal(nsIURI* aURI, bool aIsDynamic, bool* aShouldReturn,
+ bool* aFailureFromContent);
+
+ nsresult ApplyPersistentAttributes();
+ nsresult ApplyPersistentAttributesInternal();
+ nsresult ApplyPersistentAttributesToElements(const nsAString &aID,
+ nsCOMArray<nsIContent>& aElements);
+
+ nsresult
+ AddElementToDocumentPre(Element* aElement);
+
+ nsresult
+ AddElementToDocumentPost(Element* aElement);
+
+ nsresult
+ ExecuteOnBroadcastHandlerFor(Element* aBroadcaster,
+ Element* aListener,
+ nsIAtom* aAttr);
+
+ nsresult
+ BroadcastAttributeChangeFromOverlay(nsIContent* aNode,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ nsIAtom* aPrefix,
+ const nsAString& aValue);
+
+ already_AddRefed<nsPIWindowRoot> GetWindowRoot();
+
+ static void DirectionChanged(const char* aPrefName, void* aData);
+
+ // pseudo constants
+ static int32_t gRefCnt;
+
+ static nsIAtom** kIdentityAttrs[];
+
+ static nsIRDFService* gRDFService;
+ static nsIRDFResource* kNC_persist;
+ static nsIRDFResource* kNC_attribute;
+ static nsIRDFResource* kNC_value;
+
+ static LazyLogModule gXULLog;
+
+ nsresult
+ Persist(nsIContent* aElement, int32_t aNameSpaceID, nsIAtom* aAttribute);
+ // Just like Persist but ignores the return value so we can use it
+ // as a runnable method.
+ void DoPersist(nsIContent* aElement, int32_t aNameSpaceID, nsIAtom* aAttribute)
+ {
+ Persist(aElement, aNameSpaceID, aAttribute);
+ }
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ // IMPORTANT: The ownership implicit in the following member
+ // variables has been explicitly checked and set using nsCOMPtr
+ // for owning pointers and raw COM interface pointers for weak
+ // (ie, non owning) references. If you add any members to this
+ // class, please make the ownership explicit (pinkerton, scc).
+ // NOTE, THIS IS STILL IN PROGRESS, TALK TO PINK OR SCC BEFORE
+ // CHANGING
+
+ XULDocument* mNextSrcLoadWaiter; // [OWNER] but not COMPtr
+
+ // Tracks elements with a 'ref' attribute, or an 'id' attribute where
+ // the element's namespace has no registered ID attribute name.
+ nsTHashtable<nsRefMapEntry> mRefMap;
+ nsCOMPtr<nsIXULStore> mLocalStore;
+ bool mApplyingPersistedAttrs;
+ bool mIsWritingFastLoad;
+ bool mDocumentLoaded;
+ /**
+ * Since ResumeWalk is interruptible, it's possible that last
+ * stylesheet finishes loading while the PD walk is still in
+ * progress (waiting for an overlay to finish loading).
+ * mStillWalking prevents DoneLoading (and StartLayout) from being
+ * called in this situation.
+ */
+ bool mStillWalking;
+
+ /**
+ * These two values control where persistent attributes get applied.
+ */
+ bool mRestrictPersistence;
+ nsTHashtable<nsStringHashKey> mPersistenceIds;
+
+ /**
+ * An array of style sheets, that will be added (preserving order) to the
+ * document after all of them are loaded (in DoneWalking).
+ */
+ nsTArray<RefPtr<StyleSheet>> mOverlaySheets;
+
+ nsCOMPtr<nsIDOMXULCommandDispatcher> mCommandDispatcher; // [OWNER] of the focus tracker
+
+ // Maintains the template builders that have been attached to
+ // content elements
+ typedef nsInterfaceHashtable<nsISupportsHashKey, nsIXULTemplateBuilder>
+ BuilderTable;
+ BuilderTable* mTemplateBuilderTable;
+
+ uint32_t mPendingSheets;
+
+ /**
+ * document lightweight theme for use with :-moz-lwtheme, :-moz-lwtheme-brighttext
+ * and :-moz-lwtheme-darktext
+ */
+ DocumentTheme mDocLWTheme;
+
+ /**
+ * Context stack, which maintains the state of the Builder and allows
+ * it to be interrupted.
+ */
+ class ContextStack {
+ protected:
+ struct Entry {
+ nsXULPrototypeElement* mPrototype;
+ nsIContent* mElement;
+ int32_t mIndex;
+ Entry* mNext;
+ };
+
+ Entry* mTop;
+ int32_t mDepth;
+
+ public:
+ ContextStack();
+ ~ContextStack();
+
+ int32_t Depth() { return mDepth; }
+
+ nsresult Push(nsXULPrototypeElement* aPrototype, nsIContent* aElement);
+ nsresult Pop();
+ nsresult Peek(nsXULPrototypeElement** aPrototype, nsIContent** aElement, int32_t* aIndex);
+
+ nsresult SetTopIndex(int32_t aIndex);
+ };
+
+ friend class ContextStack;
+ ContextStack mContextStack;
+
+ enum State { eState_Master, eState_Overlay };
+ State mState;
+
+ /**
+ * An array of overlay nsIURIs that have yet to be resolved. The
+ * order of the array is significant: overlays at the _end_ of the
+ * array are resolved before overlays earlier in the array (i.e.,
+ * it is a stack).
+ *
+ * In the current implementation the order the overlays are loaded
+ * in is as follows: first overlays from xul-overlay PIs, in the
+ * same order as in the document, then the overlays from the chrome
+ * registry.
+ */
+ nsTArray<nsCOMPtr<nsIURI> > mUnloadedOverlays;
+
+ /**
+ * Load the transcluded script at the specified URI. If the
+ * prototype construction must 'block' until the load has
+ * completed, aBlock will be set to true.
+ */
+ nsresult LoadScript(nsXULPrototypeScript *aScriptProto, bool* aBlock);
+
+ /**
+ * Execute the precompiled script object scoped by this XUL document's
+ * containing window object.
+ */
+ nsresult ExecuteScript(nsXULPrototypeScript *aScript);
+
+ /**
+ * Create a delegate content model element from a prototype.
+ * Note that the resulting content node is not bound to any tree
+ */
+ nsresult CreateElementFromPrototype(nsXULPrototypeElement* aPrototype,
+ Element** aResult,
+ bool aIsRoot);
+
+ /**
+ * Create a hook-up element to which content nodes can be attached for
+ * later resolution.
+ */
+ nsresult CreateOverlayElement(nsXULPrototypeElement* aPrototype,
+ Element** aResult);
+
+ /**
+ * Add attributes from the prototype to the element.
+ */
+ nsresult AddAttributes(nsXULPrototypeElement* aPrototype, nsIContent* aElement);
+
+ /**
+ * The prototype-script of the current transcluded script that is being
+ * loaded. For document.write('<script src="nestedwrite.js"><\/script>')
+ * to work, these need to be in a stack element type, and we need to hold
+ * the top of stack here.
+ */
+ nsXULPrototypeScript* mCurrentScriptProto;
+
+ /**
+ * Whether the current transcluded script is being compiled off thread.
+ * The load event is blocked while this is in progress.
+ */
+ bool mOffThreadCompiling;
+
+ /**
+ * If the current transcluded script is being compiled off thread, the
+ * source for that script.
+ */
+ char16_t* mOffThreadCompileStringBuf;
+ size_t mOffThreadCompileStringLength;
+
+ /**
+ * Check if a XUL template builder has already been hooked up.
+ */
+ static nsresult
+ CheckTemplateBuilderHookup(nsIContent* aElement, bool* aNeedsHookup);
+
+ /**
+ * Create a XUL template builder on the specified node.
+ */
+ static nsresult
+ CreateTemplateBuilder(nsIContent* aElement);
+
+ /**
+ * Add the current prototype's style sheets (currently it's just
+ * style overlays from the chrome registry) to the document.
+ */
+ nsresult AddPrototypeSheets();
+
+
+protected:
+ /* Declarations related to forward references.
+ *
+ * Forward references are declarations which are added to the temporary
+ * list (mForwardReferences) during the document (or overlay) load and
+ * are resolved later, when the document loading is almost complete.
+ */
+
+ /**
+ * The list of different types of forward references to resolve. After
+ * a reference is resolved, it is removed from this array (and
+ * automatically deleted)
+ */
+ nsTArray<nsAutoPtr<nsForwardReference> > mForwardReferences;
+
+ /** Indicates what kind of forward references are still to be processed. */
+ nsForwardReference::Phase mResolutionPhase;
+
+ /**
+ * Adds aRef to the mForwardReferences array. Takes the ownership of aRef.
+ */
+ nsresult AddForwardReference(nsForwardReference* aRef);
+
+ /**
+ * Resolve all of the document's forward references.
+ */
+ nsresult ResolveForwardReferences();
+
+ /**
+ * Used to resolve broadcaster references
+ */
+ class BroadcasterHookup : public nsForwardReference
+ {
+ protected:
+ XULDocument* mDocument; // [WEAK]
+ RefPtr<Element> mObservesElement; // [OWNER]
+ bool mResolved;
+
+ public:
+ BroadcasterHookup(XULDocument* aDocument,
+ Element* aObservesElement)
+ : mDocument(aDocument),
+ mObservesElement(aObservesElement),
+ mResolved(false)
+ {
+ }
+
+ virtual ~BroadcasterHookup();
+
+ virtual Phase GetPhase() override { return eHookup; }
+ virtual Result Resolve() override;
+ };
+
+ friend class BroadcasterHookup;
+
+
+ /**
+ * Used to hook up overlays
+ */
+ class OverlayForwardReference : public nsForwardReference
+ {
+ protected:
+ XULDocument* mDocument; // [WEAK]
+ nsCOMPtr<nsIContent> mOverlay; // [OWNER]
+ bool mResolved;
+
+ nsresult Merge(nsIContent* aTargetNode, nsIContent* aOverlayNode, bool aNotify);
+
+ public:
+ OverlayForwardReference(XULDocument* aDocument, nsIContent* aOverlay)
+ : mDocument(aDocument), mOverlay(aOverlay), mResolved(false) {}
+
+ virtual ~OverlayForwardReference();
+
+ virtual Phase GetPhase() override { return eConstruction; }
+ virtual Result Resolve() override;
+ };
+
+ friend class OverlayForwardReference;
+
+ class TemplateBuilderHookup : public nsForwardReference
+ {
+ protected:
+ nsCOMPtr<nsIContent> mElement; // [OWNER]
+
+ public:
+ explicit TemplateBuilderHookup(nsIContent* aElement)
+ : mElement(aElement) {}
+
+ virtual Phase GetPhase() override { return eHookup; }
+ virtual Result Resolve() override;
+ };
+
+ friend class TemplateBuilderHookup;
+
+ // The out params of FindBroadcaster only have values that make sense when
+ // the method returns NS_FINDBROADCASTER_FOUND. In all other cases, the
+ // values of the out params should not be relied on (though *aListener and
+ // *aBroadcaster do need to be released if non-null, of course).
+ nsresult
+ FindBroadcaster(Element* aElement,
+ Element** aListener,
+ nsString& aBroadcasterID,
+ nsString& aAttribute,
+ Element** aBroadcaster);
+
+ nsresult
+ CheckBroadcasterHookup(Element* aElement,
+ bool* aNeedsHookup,
+ bool* aDidResolve);
+
+ void
+ SynchronizeBroadcastListener(Element *aBroadcaster,
+ Element *aListener,
+ const nsAString &aAttr);
+
+ static
+ nsresult
+ InsertElement(nsINode* aParent, nsIContent* aChild, bool aNotify);
+
+ static
+ nsresult
+ RemoveElement(nsINode* aParent, nsINode* aChild);
+
+ /**
+ * The current prototype that we are walking to construct the
+ * content model.
+ */
+ RefPtr<nsXULPrototypeDocument> mCurrentPrototype;
+
+ /**
+ * The master document (outermost, .xul) prototype, from which
+ * all subdocuments get their security principals.
+ */
+ RefPtr<nsXULPrototypeDocument> mMasterPrototype;
+
+ /**
+ * Owning references to all of the prototype documents that were
+ * used to construct this document.
+ */
+ nsTArray< RefPtr<nsXULPrototypeDocument> > mPrototypes;
+
+ /**
+ * Prepare to walk the current prototype.
+ */
+ nsresult PrepareToWalk();
+
+ /**
+ * Creates a processing instruction based on aProtoPI and inserts
+ * it to the DOM (as the aIndex-th child of aParent).
+ */
+ nsresult
+ CreateAndInsertPI(const nsXULPrototypePI* aProtoPI,
+ nsINode* aParent, uint32_t aIndex);
+
+ /**
+ * Inserts the passed <?xml-stylesheet ?> PI at the specified
+ * index. Loads and applies the associated stylesheet
+ * asynchronously.
+ * The prototype document walk can happen before the stylesheets
+ * are loaded, but the final steps in the load process (see
+ * DoneWalking()) are not run before all the stylesheets are done
+ * loading.
+ */
+ nsresult
+ InsertXMLStylesheetPI(const nsXULPrototypePI* aProtoPI,
+ nsINode* aParent,
+ uint32_t aIndex,
+ nsIContent* aPINode);
+
+ /**
+ * Inserts the passed <?xul-overlay ?> PI at the specified index.
+ * Schedules the referenced overlay URI for further processing.
+ */
+ nsresult
+ InsertXULOverlayPI(const nsXULPrototypePI* aProtoPI,
+ nsINode* aParent,
+ uint32_t aIndex,
+ nsIContent* aPINode);
+
+ /**
+ * Add overlays from the chrome registry to the set of unprocessed
+ * overlays still to do.
+ */
+ nsresult AddChromeOverlays();
+
+ /**
+ * Resume (or initiate) an interrupted (or newly prepared)
+ * prototype walk.
+ */
+ nsresult ResumeWalk();
+
+ /**
+ * Called at the end of ResumeWalk() and from StyleSheetLoaded().
+ * Expects that both the prototype document walk is complete and
+ * all referenced stylesheets finished loading.
+ */
+ nsresult DoneWalking();
+
+ /**
+ * Report that an overlay failed to load
+ * @param aURI the URI of the overlay that failed to load
+ */
+ void ReportMissingOverlay(nsIURI* aURI);
+
+ class CachedChromeStreamListener : public nsIStreamListener {
+ protected:
+ RefPtr<XULDocument> mDocument;
+ bool mProtoLoaded;
+
+ virtual ~CachedChromeStreamListener();
+
+ public:
+ CachedChromeStreamListener(XULDocument* aDocument,
+ bool aProtoLoaded);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ };
+
+ friend class CachedChromeStreamListener;
+
+
+ class ParserObserver : public nsIRequestObserver {
+ protected:
+ RefPtr<XULDocument> mDocument;
+ RefPtr<nsXULPrototypeDocument> mPrototype;
+ virtual ~ParserObserver();
+
+ public:
+ ParserObserver(XULDocument* aDocument,
+ nsXULPrototypeDocument* aPrototype);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ };
+
+ friend class ParserObserver;
+
+ /**
+ * A map from a broadcaster element to a list of listener elements.
+ */
+ PLDHashTable* mBroadcasterMap;
+
+ nsAutoPtr<nsInterfaceHashtable<nsURIHashKey,nsIObserver> > mOverlayLoadObservers;
+ nsAutoPtr<nsInterfaceHashtable<nsURIHashKey,nsIObserver> > mPendingOverlayLoadNotifications;
+
+ bool mInitialLayoutComplete;
+
+ class nsDelayedBroadcastUpdate
+ {
+ public:
+ nsDelayedBroadcastUpdate(Element* aBroadcaster,
+ Element* aListener,
+ const nsAString &aAttr)
+ : mBroadcaster(aBroadcaster), mListener(aListener), mAttr(aAttr),
+ mSetAttr(false), mNeedsAttrChange(false) {}
+
+ nsDelayedBroadcastUpdate(Element* aBroadcaster,
+ Element* aListener,
+ nsIAtom* aAttrName,
+ const nsAString &aAttr,
+ bool aSetAttr,
+ bool aNeedsAttrChange)
+ : mBroadcaster(aBroadcaster), mListener(aListener), mAttr(aAttr),
+ mAttrName(aAttrName), mSetAttr(aSetAttr),
+ mNeedsAttrChange(aNeedsAttrChange) {}
+
+ nsDelayedBroadcastUpdate(const nsDelayedBroadcastUpdate& aOther)
+ : mBroadcaster(aOther.mBroadcaster), mListener(aOther.mListener),
+ mAttr(aOther.mAttr), mAttrName(aOther.mAttrName),
+ mSetAttr(aOther.mSetAttr), mNeedsAttrChange(aOther.mNeedsAttrChange) {}
+
+ nsCOMPtr<Element> mBroadcaster;
+ nsCOMPtr<Element> mListener;
+ // Note if mAttrName isn't used, this is the name of the attr, otherwise
+ // this is the value of the attribute.
+ nsString mAttr;
+ nsCOMPtr<nsIAtom> mAttrName;
+ bool mSetAttr;
+ bool mNeedsAttrChange;
+
+ class Comparator {
+ public:
+ static bool Equals(const nsDelayedBroadcastUpdate& a, const nsDelayedBroadcastUpdate& b) {
+ return a.mBroadcaster == b.mBroadcaster && a.mListener == b.mListener && a.mAttrName == b.mAttrName;
+ }
+ };
+ };
+
+ nsTArray<nsDelayedBroadcastUpdate> mDelayedBroadcasters;
+ nsTArray<nsDelayedBroadcastUpdate> mDelayedAttrChangeBroadcasts;
+ bool mHandlingDelayedAttrChange;
+ bool mHandlingDelayedBroadcasters;
+
+ void MaybeBroadcast();
+private:
+ // helpers
+
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_XULDocument_h
diff --git a/dom/xul/crashtests/107518-1.xml b/dom/xul/crashtests/107518-1.xml
new file mode 100644
index 0000000000..0fa41240af
--- /dev/null
+++ b/dom/xul/crashtests/107518-1.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<!DOCTYPE window>
+
+<window
+ id = "xulnote-main-window"
+ xmlns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html = "http://www.w3.org/1999/xhtml"
+ onload = "init();"
+>
+ <scrollbox>
+ <vbox style="background-color:white;">
+ <text value="hahaha 0"/>
+ <text value="hahaha 1"/>
+ <text value="hahaha 2"/>
+ <text value="hahaha 3"/>
+ <text value="hahaha 4"/>
+ <text value="hahaha 5"/>
+ <text value="hahaha 6"/>
+ <text value="hahaha 7"/>
+ <text value="hahaha 8"/>
+ <text value="hahaha 9"/>
+ <text value="hahaha 10"/>
+ <text value="hahaha 11"/>
+ <text value="hahaha 12"/>
+ <text value="hahaha 13"/>
+ <text value="hahaha 14"/>
+ <text value="hahaha 15"/>
+ <text value="hahaha 16"/>
+ <text value="hahaha 17"/>
+ <text value="hahaha 18"/>
+ <text value="hahaha 19"/>
+ </vbox>
+<scrollbar
+ id="identifier"
+ align="horizontal"
+ curpos="20"
+ maxpos="100"
+ increment="1"
+ pageincrement="10"/>
+
+ </scrollbox>
+
+ <script type="application/x-javascript">
+ <![CDATA[
+ function init()
+ {
+ }
+ ]]>
+ </script>
+
+</window>
diff --git a/dom/xul/crashtests/252448-1.xul b/dom/xul/crashtests/252448-1.xul
new file mode 100644
index 0000000000..68813e740e
--- /dev/null
+++ b/dom/xul/crashtests/252448-1.xul
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin/"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="document.documentElement.firstChild.focus();">
+ <menulist>
+ <menupopup style="-moz-binding: none;">
+ <spacer/>
+ </menupopup>
+ </menulist>
+</window>
diff --git a/dom/xul/crashtests/253479-1.xul b/dom/xul/crashtests/253479-1.xul
new file mode 100644
index 0000000000..5860d2200d
--- /dev/null
+++ b/dom/xul/crashtests/253479-1.xul
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+>
+<html:div style="position:fixed;"/>
+</window> \ No newline at end of file
diff --git a/dom/xul/crashtests/253479-2.xul b/dom/xul/crashtests/253479-2.xul
new file mode 100644
index 0000000000..5af86f1e95
--- /dev/null
+++ b/dom/xul/crashtests/253479-2.xul
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <x style="position:fixed;"/>
+</window> \ No newline at end of file
diff --git a/dom/xul/crashtests/326204-1.xul b/dom/xul/crashtests/326204-1.xul
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/xul/crashtests/326204-1.xul
diff --git a/dom/xul/crashtests/326644-1-inner.xul b/dom/xul/crashtests/326644-1-inner.xul
new file mode 100644
index 0000000000..63ed9b7212
--- /dev/null
+++ b/dom/xul/crashtests/326644-1-inner.xul
@@ -0,0 +1,34 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Testcase bug 326644 - Crash when changing enumerated properties of objects in xul">
+
+<html:script><![CDATA[
+var timers=0;
+function doe(aObj, aNested, aCurrentTimer){
+var temp =0;
+for (var i in aObj) {
+try {
+if (typeof aObj[i] == 'object') {
+ if (aNested >= 19 || aObj[i] == window.location)
+ continue;
+ setTimeout(doe,500, aObj[i], ++aNested, timers);
+ timers++;
+}
+}
+catch(e){}
+try {
+ //if (temp == 68 && aNested == 21 && aCurrentTimer >= 116) {
+ // alert(i + '-'+ aObj[i]);
+ // return;
+ // }
+ aObj[i]= i;
+ temp+=1;
+}
+catch (e) {
+
+}
+}
+}
+var s=document.getElementsByTagName('window')[0];
+setTimeout(doe,100, s, 0);
+]]></html:script>
+</window>
diff --git a/dom/xul/crashtests/326644-1.html b/dom/xul/crashtests/326644-1.html
new file mode 100644
index 0000000000..41f2cc67f2
--- /dev/null
+++ b/dom/xul/crashtests/326644-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="326644-1-inner.xul"></iframe>
+</body>
+</html>
diff --git a/dom/xul/crashtests/326644-2-inner.xul b/dom/xul/crashtests/326644-2-inner.xul
new file mode 100644
index 0000000000..efbbc1c622
--- /dev/null
+++ b/dom/xul/crashtests/326644-2-inner.xul
@@ -0,0 +1,10 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Testcase bug 326644 - Crash when changing enumerated properties of objects in xul">
+<html:script><![CDATA[
+function doe() {
+document.documentElement.boxObject.firstChild.hidden = true;
+document.documentElement.boxObject.firstChild.tooltip = 'test';
+}
+setTimeout(doe,100);
+]]></html:script>
+</window>
diff --git a/dom/xul/crashtests/326644-2.html b/dom/xul/crashtests/326644-2.html
new file mode 100644
index 0000000000..68fd49c75d
--- /dev/null
+++ b/dom/xul/crashtests/326644-2.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="326644-2-inner.xul"></iframe>
+</body>
+</html>
diff --git a/dom/xul/crashtests/326864-1.xul b/dom/xul/crashtests/326864-1.xul
new file mode 100644
index 0000000000..5515ca2592
--- /dev/null
+++ b/dom/xul/crashtests/326864-1.xul
@@ -0,0 +1,34 @@
+<?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 getAnonymousNodes(e) {
+ return SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(e));
+}
+
+function init()
+{
+ var tt = document.getElementById("textbox");
+ var hb = getAnonymousNodes(tt)[0]; // hbox
+ var men = getAnonymousNodes(hb)[1]; // menupopup
+ var menitem = men.childNodes[0]; // menuitem
+ var hb2 = getAnonymousNodes(menitem)[1]; // hbox
+ var label2 = hb2.childNodes[0]; // label
+
+ men.menu = null;
+ label2.click();
+}
+
+window.addEventListener("load", init, false);
+
+</script>
+
+
+<textbox id="textbox"/>
+
+
+</window>
diff --git a/dom/xul/crashtests/326875-1.xul b/dom/xul/crashtests/326875-1.xul
new file mode 100644
index 0000000000..e4c3f1d6df
--- /dev/null
+++ b/dom/xul/crashtests/326875-1.xul
@@ -0,0 +1,27 @@
+<?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 init() {
+
+ var m = document.getElementById("m");
+ m.parentNode.removeChild(m);
+ m.controllers;
+};
+
+
+window.addEventListener("load", init, false);
+
+</script>
+
+
+
+<hbox id="m" />
+
+</window>
diff --git a/dom/xul/crashtests/326881-1.xul b/dom/xul/crashtests/326881-1.xul
new file mode 100644
index 0000000000..021da398a7
--- /dev/null
+++ b/dom/xul/crashtests/326881-1.xul
@@ -0,0 +1,13 @@
+<?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 init() {
+ var n1 = document.getElementsByTagName("textbox")[0];
+ n1.cloneNode(false);
+}
+
+window.addEventListener("load", init, false);
+ </script>
+
+<textbox/>
+ </window> \ No newline at end of file
diff --git a/dom/xul/crashtests/329982-1.xhtml b/dom/xul/crashtests/329982-1.xhtml
new file mode 100644
index 0000000000..43f374a57e
--- /dev/null
+++ b/dom/xul/crashtests/329982-1.xhtml
@@ -0,0 +1,42 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script>
+
+function init()
+{
+ var A = document.getElementById("z");
+ var B = A.nextSibling;
+ var C = B.nextSibling;
+ var P = A.parentNode;
+
+ document.addEventListener("DOMNodeRemoved", fizzy, false);
+ P.removeChild(B);
+ document.removeEventListener("DOMNodeRemoved", fizzy, false);
+
+ function fizzy()
+ {
+ document.removeEventListener("DOMNodeRemoved", fizzy, false); // avoid recursion
+ P.removeChild(A);
+ }
+
+ document.documentElement.appendChild(C);
+}
+
+
+window.addEventListener("load", init, false);
+
+</script>
+
+</head>
+
+<body>
+
+<hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<menupopup y="x"> <menuitem id="z"/> <menuitem/> <menuitem/> </menupopup>
+
+</hbox>
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/xul/crashtests/336096-1.xhtml b/dom/xul/crashtests/336096-1.xhtml
new file mode 100644
index 0000000000..e15691d88d
--- /dev/null
+++ b/dom/xul/crashtests/336096-1.xhtml
@@ -0,0 +1,41 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+<![CDATA[
+
+function init()
+{
+ var targetWindow = window.frames[0];
+ var targetDocument = targetWindow.document;
+
+ targetDocument.body.appendChild(document.getElementById('rootish'));
+ targetDocument.designMode = 'on';
+
+ var r = targetDocument.createRange();
+ r.setStart(targetDocument.getElementById("start"), 0);
+ r.setEnd (targetDocument.getElementById("end"), 0);
+ targetWindow.getSelection().addRange(r);
+
+ targetDocument.execCommand('bold', false, null);
+}
+
+]]>
+</script>
+</head>
+
+<body onload="setTimeout(init, 200);">
+
+<iframe src="data:text/html," style="width: 95%; height: 500px;"></iframe>
+
+<div id="rootish">
+ <div id="start"></div>
+ <hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <hbox/>
+ <vbox id="end">
+ <hbox/>
+ </vbox>
+ </hbox>
+</div>
+
+</body>
+</html>
diff --git a/dom/xul/crashtests/344215-1.xul b/dom/xul/crashtests/344215-1.xul
new file mode 100644
index 0000000000..6443c22d6c
--- /dev/null
+++ b/dom/xul/crashtests/344215-1.xul
@@ -0,0 +1,7 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<observes/>
+
+<description>You should not see any assertions in a debug build.</description>
+
+</window> \ No newline at end of file
diff --git a/dom/xul/crashtests/354611-1.html b/dom/xul/crashtests/354611-1.html
new file mode 100644
index 0000000000..fe25de3660
--- /dev/null
+++ b/dom/xul/crashtests/354611-1.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<script>
+
+var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+function boom()
+{
+ var z = document.createElementNS(XUL_NS, "window");
+ document.body.appendChild(z);
+ z.setAttribute("hidechrome", "true");
+}
+
+</script>
+
+<body onload="boom();">
+
+</body>
+
+</html> \ No newline at end of file
diff --git a/dom/xul/crashtests/360078-1.xhtml b/dom/xul/crashtests/360078-1.xhtml
new file mode 100644
index 0000000000..a29087014d
--- /dev/null
+++ b/dom/xul/crashtests/360078-1.xhtml
@@ -0,0 +1,42 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+
+<style>
+<![CDATA[
+#baz { -moz-binding: url(360078-1xbl.xml#foo); }
+]]>
+</style>
+
+<script>
+<![CDATA[
+
+function stuff()
+{
+ var baz = document.getElementById("baz");
+ var count = 0;
+
+ setTimeout(step, 30);
+
+ function step()
+ {
+ ++count;
+ if (count < 15) {
+ baz.cloneNode(true);
+ setTimeout(step, 30);
+ }
+ else {
+ document.documentElement.removeAttribute("class");
+ }
+ }
+
+}
+
+]]>
+</script>
+</head>
+<body onload="stuff()">
+
+<hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="baz"></hbox>
+
+</body>
+</html>
diff --git a/dom/xul/crashtests/360078-1xbl.xml b/dom/xul/crashtests/360078-1xbl.xml
new file mode 100644
index 0000000000..c0f428c056
--- /dev/null
+++ b/dom/xul/crashtests/360078-1xbl.xml
@@ -0,0 +1,3 @@
+<bindings xmlns="http://www.mozilla.org/xbl"><binding id="foo"><content>
+<a xmlns="http://www.w3.org/1999/xhtml" href="http://www.mozilla.org/" id="mlink">Foo<children xmlns="http://www.mozilla.org/xbl"/></a>
+</content></binding></bindings>
diff --git a/dom/xul/crashtests/363791-1.xul b/dom/xul/crashtests/363791-1.xul
new file mode 100644
index 0000000000..08d2c99e4b
--- /dev/null
+++ b/dom/xul/crashtests/363791-1.xul
@@ -0,0 +1,44 @@
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="setTimeout(boom, 30);" class="reftest-wait">
+
+<script>
+
+function boom()
+{
+ var tree = document.getElementById("tree");
+ var treecols = document.getElementById("treecols");
+ var treechildren = document.getElementById("treechildren");
+
+ tree.appendChild(treechildren); // no real change
+
+ function boom2() {
+ treecols.parentNode.removeChild(treecols);
+ document.documentElement.removeAttribute("class");
+ }
+
+ setTimeout(boom2, 30);
+}
+
+</script>
+
+
+<tree rows="6" id="tree">
+
+ <treecols id="treecols">
+ <treecol id="firstname" label="First Name"/>
+ </treecols>
+
+ <treechildren id="treechildren">
+ <treeitem>
+ <treerow>
+ <treecell label="Bob"/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+
+</tree>
+
+
+</window>
diff --git a/dom/xul/crashtests/384740-1.xul b/dom/xul/crashtests/384740-1.xul
new file mode 100644
index 0000000000..374f47c3db
--- /dev/null
+++ b/dom/xul/crashtests/384740-1.xul
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom()">
+
+<script>
+
+function boom()
+{
+ var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ var scrollbar = document.createElementNS(XUL_NS, 'scrollbar');
+ document.documentElement.appendChild(scrollbar);
+ var sbb = SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(scrollbar))[0];
+ var action = document.createElementNS(XUL_NS, 'action');
+ action.setAttribute('datasources', "");
+ sbb.appendChild(action);
+}
+
+</script>
+
+</window>
diff --git a/dom/xul/crashtests/384877-1-inner.xul b/dom/xul/crashtests/384877-1-inner.xul
new file mode 100644
index 0000000000..9bbfc07bd8
--- /dev/null
+++ b/dom/xul/crashtests/384877-1-inner.xul
@@ -0,0 +1,15 @@
+<menupopup xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="d" popup="d">
+<script>
+function doe() {
+var d = document.getElementById('d');
+if (d.openPopup) {
+ d.openPopup(document.documentElement, 'before_start', 0, 0, false, false);
+// alert(d.state);
+}
+
+if (d.showPopup)
+ d.showPopup();
+}
+setTimeout(doe, 200);
+</script>
+</menupopup>
diff --git a/dom/xul/crashtests/384877-1.html b/dom/xul/crashtests/384877-1.html
new file mode 100644
index 0000000000..28fd2fc58b
--- /dev/null
+++ b/dom/xul/crashtests/384877-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="384877-1-inner.xul"></iframe>
+</body>
+</html>
diff --git a/dom/xul/crashtests/386914-1-inner.xul b/dom/xul/crashtests/386914-1-inner.xul
new file mode 100644
index 0000000000..909889d8b5
--- /dev/null
+++ b/dom/xul/crashtests/386914-1-inner.xul
@@ -0,0 +1,10 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="a">
+
+<box id="b" observes="a"/>
+
+<html:script xmlns:html="http://www.w3.org/1999/xhtml">
+document.getElementById('b').addEventListener('DOMAttrModified', function(e) {document.removeChild(document.documentElement);}, true);
+setTimeout(function() {document.getElementById('a').setAttribute('tabindex', '1') ;}, 100);
+</html:script>
+
+</window> \ No newline at end of file
diff --git a/dom/xul/crashtests/386914-1.html b/dom/xul/crashtests/386914-1.html
new file mode 100644
index 0000000000..a8961339b7
--- /dev/null
+++ b/dom/xul/crashtests/386914-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="386914-1-inner.xul"></iframe>
+</body>
+</html>
diff --git a/dom/xul/crashtests/386947-1.xul b/dom/xul/crashtests/386947-1.xul
new file mode 100644
index 0000000000..cf00d35f24
--- /dev/null
+++ b/dom/xul/crashtests/386947-1.xul
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="background-color: red" onload="boom();">
+
+<script><![CDATA[
+
+function boom() {
+ document.documentElement.style.MozBinding = "url('#t')";
+}
+
+]]></script>
+</window>
diff --git a/dom/xul/crashtests/425821-1.xul b/dom/xul/crashtests/425821-1.xul
new file mode 100644
index 0000000000..9764e64aad
--- /dev/null
+++ b/dom/xul/crashtests/425821-1.xul
@@ -0,0 +1,15 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="boom();">
+<script type="text/javascript">
+
+function boom()
+{
+ var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var wiz = document.createElementNS(XUL_NS, "wizard");
+ var btn = document.createElementNS(XUL_NS, "hbox");
+ btn.setAttribute("anonid", "Buttons");
+ wiz.appendChild(btn);
+ wiz.cloneNode(true);
+}
+
+</script>
+</window>
diff --git a/dom/xul/crashtests/428951-1.xul b/dom/xul/crashtests/428951-1.xul
new file mode 100644
index 0000000000..024bc5d5cd
--- /dev/null
+++ b/dom/xul/crashtests/428951-1.xul
@@ -0,0 +1,21 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:mathml="http://www.w3.org/1998/Math/MathML">
+<box>
+ <box style="background: initial;" id="f">
+ <box style="margin-top: -9999999px;"/>
+ </box>
+ <mathml:divergence>
+ <box/>
+ </mathml:divergence>
+ <mathml:moment command="f"/>
+</box>
+
+<script id="script" xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+function init() {
+ var f = document.getElementsByTagName('mathml:divergence')[0];
+ window.addEventListener('DOMAttrModified',function() { f.parentNode.removeChild(f);}, true);
+ var x=document.getElementsByTagName('mathml:moment')[0];
+ x.parentNode.removeChild(x);
+}
+window.addEventListener("load", init, false);
+]]></script>
+</window>
diff --git a/dom/xul/crashtests/429085-1.xhtml b/dom/xul/crashtests/429085-1.xhtml
new file mode 100644
index 0000000000..96e030fd42
--- /dev/null
+++ b/dom/xul/crashtests/429085-1.xhtml
@@ -0,0 +1,21 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var prefs = document.createElementNS(XUL_NS, "preferences");
+ var textbox = document.createElementNS(XUL_NS, "textbox");
+ textbox.setAttribute("onchange", "1");
+ prefs.appendChild(textbox);
+ prefs.cloneNode(true);
+}
+
+]]>
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/xul/crashtests/431906-1-inner.xul b/dom/xul/crashtests/431906-1-inner.xul
new file mode 100644
index 0000000000..367f621b2f
--- /dev/null
+++ b/dom/xul/crashtests/431906-1-inner.xul
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<colgroup id="a" command="a">
+<box xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<box id="a" command="a"/>
+<box id="a" command="a"/>
+</box>
+</colgroup>
+<script><![CDATA[
+function doe(){
+document.getElementsByTagName('*')[1].setAttribute('id','');
+document.getElementsByTagName('*')[1].setAttribute('xmlns', '');
+document.getElementsByTagName('*')[3].setAttribute('id','');
+
+document.getElementsByTagName('*')[4].removeAttribute('xmlns');
+document.getElementsByTagName('*')[4].setAttribute('width', '1px');
+}
+setTimeout(doe,100);
+]]></script>
+</html> \ No newline at end of file
diff --git a/dom/xul/crashtests/431906-1.html b/dom/xul/crashtests/431906-1.html
new file mode 100644
index 0000000000..1639d3ea66
--- /dev/null
+++ b/dom/xul/crashtests/431906-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="431906-1-inner.xul"></iframe>
+</body>
+</html>
diff --git a/dom/xul/crashtests/451311-1.xul b/dom/xul/crashtests/451311-1.xul
new file mode 100644
index 0000000000..f07928b66a
--- /dev/null
+++ b/dom/xul/crashtests/451311-1.xul
@@ -0,0 +1 @@
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><iframe/></overlay>
diff --git a/dom/xul/crashtests/461917-1.xhtml b/dom/xul/crashtests/461917-1.xhtml
new file mode 100644
index 0000000000..15792f6f0f
--- /dev/null
+++ b/dom/xul/crashtests/461917-1.xhtml
@@ -0,0 +1,6 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head></head>
+<body>
+<tabs xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onselect="#"><wizard/></tabs>
+</body>
+</html>
diff --git a/dom/xul/crashtests/468211-1.xul b/dom/xul/crashtests/468211-1.xul
new file mode 100644
index 0000000000..b6364f1bf1
--- /dev/null
+++ b/dom/xul/crashtests/468211-1.xul
@@ -0,0 +1,23 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" style="-moz-binding:url(#xbl2)" class="reftest-wait">
+<tooltip/>
+
+<script id="script" xmlns="http://www.w3.org/1999/xhtml">
+function doe() {
+document.getElementsByTagName('tooltip')[0].setAttribute('style', '-moz-binding:url(#xbl)');
+document.getElementsByTagName('tooltip')[0].setAttribute('onDOMAttrModified', 'this.focus()');
+document.documentElement.removeAttribute("class");
+}
+setTimeout(doe, 1);
+</script>
+
+<bindings xmlns="http://www.mozilla.org/xbl" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<binding id="xbl">
+<content>
+<xul:box onbroadcast="document.documentElement.setAttribute('a','a')" id="g"/>
+<xul:box onDOMAttrModified="document.documentElement.style.display = 'none'" observes="g"/>
+</content>
+</binding>
+
+<binding id="xbl2"></binding>
+</bindings>
+</window> \ No newline at end of file
diff --git a/dom/xul/crashtests/468211-2-binding.xml b/dom/xul/crashtests/468211-2-binding.xml
new file mode 100644
index 0000000000..6a9d14f132
--- /dev/null
+++ b/dom/xul/crashtests/468211-2-binding.xml
@@ -0,0 +1,12 @@
+<bindings xmlns="http://www.mozilla.org/xbl">
+
+<binding id="xbl">
+
+<content>
+<mrow xmlns="http://www.w3.org/1998/Math/MathML" id="f">
+<box observes="f" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
+</mrow>
+</content>
+
+</binding>
+</bindings> \ No newline at end of file
diff --git a/dom/xul/crashtests/468211-2.xul b/dom/xul/crashtests/468211-2.xul
new file mode 100644
index 0000000000..ca3104875f
--- /dev/null
+++ b/dom/xul/crashtests/468211-2.xul
@@ -0,0 +1,10 @@
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+<html:div onDOMAttrModified="this.parentNode.removeChild(this)">
+<html:div/>
+<html:style>div {-moz-binding:url(468211-2-binding.xml#xbl);</html:style>
+</html:div>
+
+</window> \ No newline at end of file
diff --git a/dom/xul/crashtests/468211-3.xul b/dom/xul/crashtests/468211-3.xul
new file mode 100644
index 0000000000..4d221616e0
--- /dev/null
+++ b/dom/xul/crashtests/468211-3.xul
@@ -0,0 +1,18 @@
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<box style="-moz-binding:url(#xbl)"/>
+
+
+<bindings xmlns="http://www.mozilla.org/xbl">
+<binding id="xbl" inheritstyle="false">
+<content>
+<g xmlns="http://www.w3.org/2000/svg" onDOMAttrModified="document.removeChild(document.documentElement)" id="a"/>
+<box xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" observes="a"/>
+
+<children xmlns="http://www.mozilla.org/xbl"/>
+
+</content>
+</binding>
+</bindings>
+
+</window> \ No newline at end of file
diff --git a/dom/xul/crashtests/495635-1.xul b/dom/xul/crashtests/495635-1.xul
new file mode 100644
index 0000000000..3d42a10bbe
--- /dev/null
+++ b/dom/xul/crashtests/495635-1.xul
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<?xul-overlay href="extA1.xul"?>
+<?xul-overlay href="extB1.xul"?>
+<?xul-overlay href="extA2.xul"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<vbox id="browser-bottombox">
+</vbox>
+</window>
diff --git a/dom/xul/crashtests/509719-1-overlay.xul b/dom/xul/crashtests/509719-1-overlay.xul
new file mode 100644
index 0000000000..880edc1cd4
--- /dev/null
+++ b/dom/xul/crashtests/509719-1-overlay.xul
@@ -0,0 +1,3 @@
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <window id="win" removeelement="true"/>
+</overlay>
diff --git a/dom/xul/crashtests/509719-1.xul b/dom/xul/crashtests/509719-1.xul
new file mode 100644
index 0000000000..4d869b323a
--- /dev/null
+++ b/dom/xul/crashtests/509719-1.xul
@@ -0,0 +1,3 @@
+<?xul-overlay href="509719-1-overlay.xul"?>
+<window id="win" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+</window>
diff --git a/dom/xul/crashtests/509719-2-overlay.xul b/dom/xul/crashtests/509719-2-overlay.xul
new file mode 100644
index 0000000000..fefbe194aa
--- /dev/null
+++ b/dom/xul/crashtests/509719-2-overlay.xul
@@ -0,0 +1,8 @@
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script>
+ if (document.getElementById("testnode")) {
+ document.loadOverlay(window.location.href.substr(0,window.location.href.lastIndexOf('/')+1)+'509719-2-overlay.xul', null);
+ }
+ </script>
+ <box xmlns="http://www.w3.org/1999/xhtml" id="testnode" removeelement="true"/>
+</overlay>
diff --git a/dom/xul/crashtests/509719-2.xul b/dom/xul/crashtests/509719-2.xul
new file mode 100644
index 0000000000..5a64dc76ed
--- /dev/null
+++ b/dom/xul/crashtests/509719-2.xul
@@ -0,0 +1,7 @@
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<window id="win" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <box id="testnode" onDOMAttrModified="this.parentNode.removeChild(this)"/>
+ <script>
+document.loadOverlay(window.location.href.substr(0,window.location.href.lastIndexOf('/')+1)+'509719-2-overlay.xul', null);
+ </script>
+</window>
diff --git a/dom/xul/crashtests/583230.xul b/dom/xul/crashtests/583230.xul
new file mode 100644
index 0000000000..0361b619d8
--- /dev/null
+++ b/dom/xul/crashtests/583230.xul
@@ -0,0 +1,23 @@
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<richlistbox id="a" datasources="" template="d"/>
+<script><![CDATA[
+function doe() {
+var node = document.getElementById('a');
+var b = node.builder;
+document.removeChild(document.documentElement);
+b.addResult({}, node);
+b.removeResult({});
+b.replaceResult({}, {}, node);
+b.resultBindingChanged({});
+try { b.addResult(null, null); } catch(ex) { }
+try { b.removeResult(null); } catch(ex) { }
+try { b.replaceResult(null, null, null); } catch(ex) { }
+try { b.resultBindingChanged(null); } catch(ex) { }
+try { b.getResultForId("empty"); } catch(ex) { }
+try { b.getResultForContent(node); } catch(ex) { }
+try { b.hasGeneratedContent(null, null); } catch(ex) { }
+}
+window.addEventListener("load", doe, false);
+]]></script>
+</window>
diff --git a/dom/xul/crashtests/crashtests.list b/dom/xul/crashtests/crashtests.list
new file mode 100644
index 0000000000..737a32f60f
--- /dev/null
+++ b/dom/xul/crashtests/crashtests.list
@@ -0,0 +1,33 @@
+load 107518-1.xml
+load 252448-1.xul
+load 253479-1.xul
+load 253479-2.xul
+load 326204-1.xul
+load 326644-1.html
+load 326644-2.html
+load 326864-1.xul
+load 326875-1.xul
+load 326881-1.xul
+load 329982-1.xhtml
+load 336096-1.xhtml
+load 344215-1.xul
+load 354611-1.html
+load 360078-1.xhtml
+load 363791-1.xul
+load 384740-1.xul
+load 384877-1.html
+load 386914-1.html
+load 386947-1.xul
+load 425821-1.xul
+load 428951-1.xul
+load 429085-1.xhtml
+load 431906-1.html
+load 451311-1.xul
+load 461917-1.xhtml
+load 468211-1.xul
+load 468211-2.xul
+load 468211-3.xul
+load 495635-1.xul
+load 509719-1.xul
+asserts(3) load 509719-2.xul # bug 909819
+load 583230.xul
diff --git a/dom/xul/crashtests/extA1.xul b/dom/xul/crashtests/extA1.xul
new file mode 100644
index 0000000000..bfd2c0d448
--- /dev/null
+++ b/dom/xul/crashtests/extA1.xul
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE overlay SYSTEM "chrome://exta/locale/exta.dtd">
+<overlay id="extA1"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <vbox id="browser-bottombox">
+ <statusbar id="extAbar" />
+ </vbox>
+</overlay>
diff --git a/dom/xul/crashtests/extA2.xul b/dom/xul/crashtests/extA2.xul
new file mode 100644
index 0000000000..8d77a4995d
--- /dev/null
+++ b/dom/xul/crashtests/extA2.xul
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE overlay SYSTEM "chrome://exta/locale/exta.dtd">
+<overlay id="extA2"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <statusbar id="extAbar">
+ <statusbarpanel id="extApanel" label="panel 1">
+ </statusbarpanel>
+ <statusbarpanel id="extApanel2" label="panel 2" />
+ </statusbar>
+</overlay>
diff --git a/dom/xul/crashtests/extB1.xul b/dom/xul/crashtests/extB1.xul
new file mode 100644
index 0000000000..9e2f5c4182
--- /dev/null
+++ b/dom/xul/crashtests/extB1.xul
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<overlay id="extA2"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <statusbar id="extAbar" removeelement="true" />
+</overlay>
diff --git a/dom/xul/moz.build b/dom/xul/moz.build
new file mode 100644
index 0000000000..8dff722bee
--- /dev/null
+++ b/dom/xul/moz.build
@@ -0,0 +1,59 @@
+# -*- 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_MANIFESTS += ['test/mochitest.ini']
+
+MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
+
+if CONFIG['MOZ_XUL']:
+ DIRS += ['templates']
+
+ XPIDL_SOURCES += [
+ 'nsIXULOverlayProvider.idl',
+ ]
+
+ EXPORTS += [
+ 'nsIXULDocument.h',
+ ]
+
+ UNIFIED_SOURCES += [
+ 'nsXULCommandDispatcher.cpp',
+ 'nsXULContentSink.cpp',
+ 'nsXULElement.cpp',
+ 'nsXULPopupListener.cpp',
+ 'nsXULPrototypeCache.cpp',
+ 'nsXULPrototypeDocument.cpp',
+ 'XULDocument.cpp',
+ ]
+
+XPIDL_SOURCES += [
+ 'nsIController.idl',
+ 'nsIControllers.idl',
+]
+
+XPIDL_MODULE = 'xul'
+
+UNIFIED_SOURCES += [
+ 'nsXULControllers.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/docshell/base',
+ '/dom/base',
+ '/dom/html',
+ '/dom/xbl',
+ '/dom/xml',
+ '/dom/xul/templates',
+ '/layout/base',
+ '/layout/generic',
+ '/layout/style',
+ '/layout/xul',
+]
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/dom/xul/nsForwardReference.h b/dom/xul/nsForwardReference.h
new file mode 100644
index 0000000000..2528662c30
--- /dev/null
+++ b/dom/xul/nsForwardReference.h
@@ -0,0 +1,76 @@
+/* -*- 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 nsForwardReference_h__
+#define nsForwardReference_h__
+
+class nsForwardReference
+{
+protected:
+ nsForwardReference() {}
+
+public:
+ virtual ~nsForwardReference() {}
+
+ /**
+ * Priority codes returned from GetPhase()
+ */
+ enum Phase {
+ /** A dummy marker, used to indicate unstarted resolution */
+ eStart,
+
+ /** The initial pass, after which the content model will be
+ fully built */
+ eConstruction,
+
+ /** A second pass, after which all 'magic attribute' hookup
+ will have been performed */
+ eHookup,
+
+ /** A dummy marker, used in kPasses to indicate termination */
+ eDone
+ };
+
+ /**
+ * Forward references are categorized by 'priority', and all
+ * forward references in a higher priority are resolved before any
+ * reference in a lower priority. This variable specifies this
+ * ordering. The last Priority is guaranteed to be eDone.
+ */
+ static const Phase kPasses[];
+
+ /**
+ * Get the state in which the forward reference should be resolved.
+ * 'eConstruction' references are all resolved before 'eHookup' references
+ * are resolved.
+ *
+ * @return the Phase in which the reference needs to be resolved
+ */
+ virtual Phase GetPhase() = 0;
+
+ /**
+ * Result codes returned from Resolve()
+ */
+ enum Result {
+ /** Resolution succeeded, I'm done. */
+ eResolve_Succeeded,
+
+ /** Couldn't resolve, but try me later. */
+ eResolve_Later,
+
+ /** Something bad happened, don't try again. */
+ eResolve_Error
+ };
+
+ /**
+ * Attempt to resolve the forward reference.
+ *
+ * @return a Result that tells the resolver how to treat
+ * the reference.
+ */
+ virtual Result Resolve() = 0;
+};
+
+#endif // nsForwardReference_h__
diff --git a/dom/xul/nsIController.idl b/dom/xul/nsIController.idl
new file mode 100644
index 0000000000..ace5374a05
--- /dev/null
+++ b/dom/xul/nsIController.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(D5B61B82-1DA4-11d3-BF87-00105A1B0627)]
+interface nsIController : nsISupports {
+ boolean isCommandEnabled(in string command);
+ boolean supportsCommand(in string command);
+
+ void doCommand(in string command);
+
+ void onEvent(in string eventName);
+};
+
+
+/*
+
+ Enhanced controller interface that allows for passing of parameters
+ to commands.
+
+*/
+
+interface nsICommandParams;
+
+[scriptable, uuid(EEC0B435-7F53-44FE-B00A-CF3EED65C01A)]
+interface nsICommandController : nsISupports
+{
+
+ void getCommandStateWithParams( in string command, in nsICommandParams aCommandParams);
+
+ void doCommandWithParams(in string command, in nsICommandParams aCommandParams);
+
+ void getSupportedCommands(out unsigned long count,
+ [array, size_is(count), retval] out string commands);
+};
+
+
+/*
+ An API for registering commands in groups, to allow for
+ updating via nsIDOMWindow::UpdateCommands.
+*/
+interface nsISimpleEnumerator;
+
+[scriptable, uuid(9F82C404-1C7B-11D5-A73C-ECA43CA836FC)]
+interface nsIControllerCommandGroup : nsISupports
+{
+
+ void addCommandToGroup(in string aCommand, in string aGroup);
+ void removeCommandFromGroup(in string aCommand, in string aGroup);
+
+ boolean isCommandInGroup(in string aCommand, in string aGroup);
+
+ /*
+ We should expose some methods that allow for enumeration.
+ */
+ nsISimpleEnumerator getGroupsEnumerator();
+
+ nsISimpleEnumerator getEnumeratorForGroup(in string aGroup);
+
+};
+
diff --git a/dom/xul/nsIControllers.idl b/dom/xul/nsIControllers.idl
new file mode 100644
index 0000000000..d9505a5e82
--- /dev/null
+++ b/dom/xul/nsIControllers.idl
@@ -0,0 +1,34 @@
+/* -*- 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 nsIController;
+interface nsIDOMXULCommandDispatcher;
+
+[scriptable, uuid(f36e3ec1-9197-4ad8-8d4c-d3b1927fd6df)]
+interface nsIControllers : nsISupports
+{
+ nsIController getControllerForCommand(in string command);
+
+ void insertControllerAt(in unsigned long index, in nsIController controller);
+ nsIController removeControllerAt(in unsigned long index);
+ nsIController getControllerAt(in unsigned long index);
+
+ void appendController(in nsIController controller);
+ void removeController(in nsIController controller);
+
+ /*
+ Return an ID for this controller which is unique to this
+ nsIControllers.
+ */
+ unsigned long getControllerId(in nsIController controller);
+ /*
+ Get the controller specified by the given ID.
+ */
+ nsIController getControllerById(in unsigned long controllerID);
+
+ unsigned long getControllerCount();
+};
diff --git a/dom/xul/nsIXULDocument.h b/dom/xul/nsIXULDocument.h
new file mode 100644
index 0000000000..944c481d93
--- /dev/null
+++ b/dom/xul/nsIXULDocument.h
@@ -0,0 +1,92 @@
+/* -*- 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 nsIXULDocument_h___
+#define nsIXULDocument_h___
+
+#include "nsISupports.h"
+#include "nsString.h"
+#include "nsCOMArray.h"
+
+class nsIXULTemplateBuilder;
+class nsIContent;
+
+
+// 81ba4be5-6cc5-478a-9b08-b3e7ed524455
+#define NS_IXULDOCUMENT_IID \
+ {0x81ba4be5, 0x6cc5, 0x478a, {0x9b, 0x08, 0xb3, 0xe7, 0xed, 0x52, 0x44, 0x55}}
+
+
+/*
+ * An XUL-specific extension to nsIDocument. Includes methods for
+ * setting the root resource of the document content model, a factory
+ * method for constructing the children of a node, etc.
+ */
+class nsIXULDocument : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IXULDOCUMENT_IID)
+
+ /**
+ * Get the elements for a particular resource --- all elements whose 'id'
+ * or 'ref' is aID. The nsCOMArray will be truncated and filled in with
+ * nsIContent pointers.
+ */
+ virtual void GetElementsForID(const nsAString& aID, nsCOMArray<nsIContent>& aElements) = 0;
+
+ /**
+ * Notify the XUL document that a subtree has been added
+ */
+ NS_IMETHOD AddSubtreeToDocument(nsIContent* aElement) = 0;
+
+ /**
+ * Notify the XUL document that a subtree has been removed
+ */
+ NS_IMETHOD RemoveSubtreeFromDocument(nsIContent* aElement) = 0;
+
+ /**
+ * Attach a XUL template builder to the specified content node.
+ * @param aBuilder the template builder to attach, or null if
+ * the builder is to be removed.
+ */
+ NS_IMETHOD SetTemplateBuilderFor(nsIContent* aContent, nsIXULTemplateBuilder* aBuilder) = 0;
+
+ /**
+ * Retrieve the XUL template builder that's attached to a content
+ * node.
+ */
+ NS_IMETHOD GetTemplateBuilderFor(nsIContent* aContent, nsIXULTemplateBuilder** aResult) = 0;
+
+ /**
+ * This is invoked whenever the prototype for this document is loaded
+ * and should be walked, regardless of whether the XUL cache is
+ * disabled, whether the protototype was loaded, whether the
+ * prototype was loaded from the cache or created by parsing the
+ * actual XUL source, etc.
+ *
+ * @param aResumeWalk whether this should also call ResumeWalk().
+ * Sometimes the caller of OnPrototypeLoadDone resumes the walk itself
+ */
+ NS_IMETHOD OnPrototypeLoadDone(bool aResumeWalk) = 0;
+
+ /**
+ * Callback notifying when a document could not be parsed properly.
+ */
+ virtual bool OnDocumentParserError() = 0;
+
+ /**
+ * Reset the document direction so that it is recomputed.
+ */
+ virtual void ResetDocumentDirection() = 0;
+
+ virtual void ResetDocumentLWTheme() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIXULDocument, NS_IXULDOCUMENT_IID)
+
+// factory functions
+nsresult NS_NewXULDocument(nsIXULDocument** result);
+
+#endif // nsIXULDocument_h___
diff --git a/dom/xul/nsIXULOverlayProvider.idl b/dom/xul/nsIXULOverlayProvider.idl
new file mode 100644
index 0000000000..f74a1717dc
--- /dev/null
+++ b/dom/xul/nsIXULOverlayProvider.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: java; tab-width: 8; 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 nsISimpleEnumerator;
+interface nsIURI;
+
+/**
+ * The chrome registry implements this interface to give overlays
+ * to the gecko XUL engine.
+ */
+
+[scriptable, uuid(1d5b5b94-dc47-4050-93b7-ac092e383cad)]
+interface nsIXULOverlayProvider : nsISupports
+{
+ /**
+ * Get the XUL overlays for a particular chrome URI.
+ *
+ * @param aURI The URI being loaded
+ * @return An enumerator of nsIURI for the overlays of this URI
+ */
+ nsISimpleEnumerator /*nsIURI*/ getXULOverlays(in nsIURI aURI);
+
+ /**
+ * Get the style overlays for a particular chrome URI.
+ *
+ * @param aURI The URI being loaded
+ * @return An enumerator of nsIURI for the overlays of this URI
+ */
+ nsISimpleEnumerator /*nsIURI*/ getStyleOverlays(in nsIURI aURI);
+};
diff --git a/dom/xul/nsXULCommandDispatcher.cpp b/dom/xul/nsXULCommandDispatcher.cpp
new file mode 100644
index 0000000000..2d222c2401
--- /dev/null
+++ b/dom/xul/nsXULCommandDispatcher.cpp
@@ -0,0 +1,459 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+
+ This file provides the implementation for the XUL Command Dispatcher.
+
+ */
+
+#include "nsIContent.h"
+#include "nsFocusManager.h"
+#include "nsIControllers.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMXULElement.h"
+#include "nsIDocument.h"
+#include "nsPresContext.h"
+#include "nsIPresShell.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsPIDOMWindow.h"
+#include "nsPIWindowRoot.h"
+#include "nsRDFCID.h"
+#include "nsXULCommandDispatcher.h"
+#include "mozilla/Logging.h"
+#include "nsContentUtils.h"
+#include "nsReadableUtils.h"
+#include "nsCRT.h"
+#include "nsError.h"
+#include "nsDOMClassInfoID.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+
+static LazyLogModule gCommandLog("nsXULCommandDispatcher");
+
+////////////////////////////////////////////////////////////////////////
+
+nsXULCommandDispatcher::nsXULCommandDispatcher(nsIDocument* aDocument)
+ : mDocument(aDocument), mUpdaters(nullptr)
+{
+}
+
+nsXULCommandDispatcher::~nsXULCommandDispatcher()
+{
+ Disconnect();
+}
+
+// QueryInterface implementation for nsXULCommandDispatcher
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULCommandDispatcher)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMXULCommandDispatcher)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMXULCommandDispatcher)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(XULCommandDispatcher)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULCommandDispatcher)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULCommandDispatcher)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULCommandDispatcher)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULCommandDispatcher)
+ tmp->Disconnect();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULCommandDispatcher)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
+ Updater* updater = tmp->mUpdaters;
+ while (updater) {
+ cb.NoteXPCOMChild(updater->mElement);
+ updater = updater->mNext;
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+void
+nsXULCommandDispatcher::Disconnect()
+{
+ while (mUpdaters) {
+ Updater* doomed = mUpdaters;
+ mUpdaters = mUpdaters->mNext;
+ delete doomed;
+ }
+ mDocument = nullptr;
+}
+
+already_AddRefed<nsPIWindowRoot>
+nsXULCommandDispatcher::GetWindowRoot()
+{
+ if (mDocument) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow()) {
+ return window->GetTopWindowRoot();
+ }
+ }
+
+ return nullptr;
+}
+
+nsIContent*
+nsXULCommandDispatcher::GetRootFocusedContentAndWindow(nsPIDOMWindowOuter** aWindow)
+{
+ *aWindow = nullptr;
+
+ if (!mDocument) {
+ return nullptr;
+ }
+
+ if (nsCOMPtr<nsPIDOMWindowOuter> win = mDocument->GetWindow()) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> rootWindow = win->GetPrivateRoot()) {
+ return nsFocusManager::GetFocusedDescendant(rootWindow, true, aWindow);
+ }
+ }
+
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::GetFocusedElement(nsIDOMElement** aElement)
+{
+ *aElement = nullptr;
+
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsIContent* focusedContent =
+ GetRootFocusedContentAndWindow(getter_AddRefs(focusedWindow));
+ if (focusedContent) {
+ CallQueryInterface(focusedContent, aElement);
+
+ // Make sure the caller can access the focused element.
+ nsCOMPtr<nsINode> node = do_QueryInterface(*aElement);
+ if (!node || !nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller()->Subsumes(node->NodePrincipal())) {
+ // XXX This might want to return null, but we use that return value
+ // to mean "there is no focused element," so to be clear, throw an
+ // exception.
+ NS_RELEASE(*aElement);
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::GetFocusedWindow(mozIDOMWindowProxy** aWindow)
+{
+ *aWindow = nullptr;
+
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ GetRootFocusedContentAndWindow(getter_AddRefs(window));
+ if (!window)
+ return NS_OK;
+
+ // Make sure the caller can access this window. The caller can access this
+ // window iff it can access the document.
+ nsCOMPtr<nsIDocument> doc = window->GetDoc();
+
+ // Note: If there is no document, then this window has been cleared and
+ // there's nothing left to protect, so let the window pass through.
+ if (doc && !nsContentUtils::CanCallerAccess(doc))
+ return NS_ERROR_DOM_SECURITY_ERR;
+
+ window.forget(aWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::SetFocusedElement(nsIDOMElement* aElement)
+{
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ NS_ENSURE_TRUE(fm, NS_ERROR_FAILURE);
+
+ if (aElement)
+ return fm->SetFocus(aElement, 0);
+
+ // if aElement is null, clear the focus in the currently focused child window
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ GetRootFocusedContentAndWindow(getter_AddRefs(focusedWindow));
+ return fm->ClearFocus(focusedWindow);
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::SetFocusedWindow(mozIDOMWindowProxy* aWindow)
+{
+ NS_ENSURE_TRUE(aWindow, NS_OK); // do nothing if set to null
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ NS_ENSURE_TRUE(fm, NS_ERROR_FAILURE);
+
+ // get the containing frame for the window, and set it as focused. This will
+ // end up focusing whatever is currently focused inside the frame. Since
+ // setting the command dispatcher's focused window doesn't raise the window,
+ // setting it to a top-level window doesn't need to do anything.
+ nsCOMPtr<nsIDOMElement> frameElement =
+ do_QueryInterface(window->GetFrameElementInternal());
+ if (frameElement)
+ return fm->SetFocus(frameElement, 0);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::AdvanceFocus()
+{
+ return AdvanceFocusIntoSubtree(nullptr);
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::RewindFocus()
+{
+ nsCOMPtr<nsPIDOMWindowOuter> win;
+ GetRootFocusedContentAndWindow(getter_AddRefs(win));
+
+ nsCOMPtr<nsIDOMElement> result;
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm)
+ return fm->MoveFocus(win, nullptr, nsIFocusManager::MOVEFOCUS_BACKWARD,
+ 0, getter_AddRefs(result));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::AdvanceFocusIntoSubtree(nsIDOMElement* aElt)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> win;
+ GetRootFocusedContentAndWindow(getter_AddRefs(win));
+
+ nsCOMPtr<nsIDOMElement> result;
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm)
+ return fm->MoveFocus(win, aElt, nsIFocusManager::MOVEFOCUS_FORWARD,
+ 0, getter_AddRefs(result));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::AddCommandUpdater(nsIDOMElement* aElement,
+ const nsAString& aEvents,
+ const nsAString& aTargets)
+{
+ NS_PRECONDITION(aElement != nullptr, "null ptr");
+ if (! aElement)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_UNEXPECTED);
+
+ nsresult rv = nsContentUtils::CheckSameOrigin(mDocument, aElement);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ Updater* updater = mUpdaters;
+ Updater** link = &mUpdaters;
+
+ while (updater) {
+ if (updater->mElement == aElement) {
+
+#ifdef DEBUG
+ if (MOZ_LOG_TEST(gCommandLog, LogLevel::Debug)) {
+ nsAutoCString eventsC, targetsC, aeventsC, atargetsC;
+ eventsC.AssignWithConversion(updater->mEvents);
+ targetsC.AssignWithConversion(updater->mTargets);
+ CopyUTF16toUTF8(aEvents, aeventsC);
+ CopyUTF16toUTF8(aTargets, atargetsC);
+ MOZ_LOG(gCommandLog, LogLevel::Debug,
+ ("xulcmd[%p] replace %p(events=%s targets=%s) with (events=%s targets=%s)",
+ this, aElement,
+ eventsC.get(),
+ targetsC.get(),
+ aeventsC.get(),
+ atargetsC.get()));
+ }
+#endif
+
+ // If the updater was already in the list, then replace
+ // (?) the 'events' and 'targets' filters with the new
+ // specification.
+ updater->mEvents = aEvents;
+ updater->mTargets = aTargets;
+ return NS_OK;
+ }
+
+ link = &(updater->mNext);
+ updater = updater->mNext;
+ }
+#ifdef DEBUG
+ if (MOZ_LOG_TEST(gCommandLog, LogLevel::Debug)) {
+ nsAutoCString aeventsC, atargetsC;
+ CopyUTF16toUTF8(aEvents, aeventsC);
+ CopyUTF16toUTF8(aTargets, atargetsC);
+
+ MOZ_LOG(gCommandLog, LogLevel::Debug,
+ ("xulcmd[%p] add %p(events=%s targets=%s)",
+ this, aElement,
+ aeventsC.get(),
+ atargetsC.get()));
+ }
+#endif
+
+ // If we get here, this is a new updater. Append it to the list.
+ *link = new Updater(aElement, aEvents, aTargets);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::RemoveCommandUpdater(nsIDOMElement* aElement)
+{
+ NS_PRECONDITION(aElement != nullptr, "null ptr");
+ if (! aElement)
+ return NS_ERROR_NULL_POINTER;
+
+ Updater* updater = mUpdaters;
+ Updater** link = &mUpdaters;
+
+ while (updater) {
+ if (updater->mElement == aElement) {
+#ifdef DEBUG
+ if (MOZ_LOG_TEST(gCommandLog, LogLevel::Debug)) {
+ nsAutoCString eventsC, targetsC;
+ eventsC.AssignWithConversion(updater->mEvents);
+ targetsC.AssignWithConversion(updater->mTargets);
+ MOZ_LOG(gCommandLog, LogLevel::Debug,
+ ("xulcmd[%p] remove %p(events=%s targets=%s)",
+ this, aElement,
+ eventsC.get(),
+ targetsC.get()));
+ }
+#endif
+
+ *link = updater->mNext;
+ delete updater;
+ return NS_OK;
+ }
+
+ link = &(updater->mNext);
+ updater = updater->mNext;
+ }
+
+ // Hmm. Not found. Oh well.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::UpdateCommands(const nsAString& aEventName)
+{
+ nsAutoString id;
+ nsCOMPtr<nsIDOMElement> element;
+ GetFocusedElement(getter_AddRefs(element));
+ if (element) {
+ nsresult rv = element->GetAttribute(NS_LITERAL_STRING("id"), id);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get element's id");
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ nsCOMArray<nsIContent> updaters;
+
+ for (Updater* updater = mUpdaters; updater != nullptr; updater = updater->mNext) {
+ // Skip any nodes that don't match our 'events' or 'targets'
+ // filters.
+ if (! Matches(updater->mEvents, aEventName))
+ continue;
+
+ if (! Matches(updater->mTargets, id))
+ continue;
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(updater->mElement);
+ NS_ASSERTION(content != nullptr, "not an nsIContent");
+ if (! content)
+ return NS_ERROR_UNEXPECTED;
+
+ updaters.AppendObject(content);
+ }
+
+ for (int32_t u = 0; u < updaters.Count(); u++) {
+ nsIContent* content = updaters[u];
+
+#ifdef DEBUG
+ if (MOZ_LOG_TEST(gCommandLog, LogLevel::Debug)) {
+ nsAutoCString aeventnameC;
+ CopyUTF16toUTF8(aEventName, aeventnameC);
+ MOZ_LOG(gCommandLog, LogLevel::Debug,
+ ("xulcmd[%p] update %p event=%s",
+ this, content,
+ aeventnameC.get()));
+ }
+#endif
+
+ WidgetEvent event(true, eXULCommandUpdate);
+ EventDispatcher::Dispatch(content, nullptr, &event);
+ }
+ return NS_OK;
+}
+
+bool
+nsXULCommandDispatcher::Matches(const nsString& aList,
+ const nsAString& aElement)
+{
+ if (aList.EqualsLiteral("*"))
+ return true; // match _everything_!
+
+ int32_t indx = aList.Find(PromiseFlatString(aElement));
+ if (indx == -1)
+ return false; // not in the list at all
+
+ // okay, now make sure it's not a substring snafu; e.g., 'ur'
+ // found inside of 'blur'.
+ if (indx > 0) {
+ char16_t ch = aList[indx - 1];
+ if (! nsCRT::IsAsciiSpace(ch) && ch != char16_t(','))
+ return false;
+ }
+
+ if (indx + aElement.Length() < aList.Length()) {
+ char16_t ch = aList[indx + aElement.Length()];
+ if (! nsCRT::IsAsciiSpace(ch) && ch != char16_t(','))
+ return false;
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::GetControllers(nsIControllers** aResult)
+{
+ nsCOMPtr<nsPIWindowRoot> root = GetWindowRoot();
+ NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
+
+ return root->GetControllers(aResult);
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::GetControllerForCommand(const char *aCommand, nsIController** _retval)
+{
+ nsCOMPtr<nsPIWindowRoot> root = GetWindowRoot();
+ NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
+
+ return root->GetControllerForCommand(aCommand, _retval);
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::GetSuppressFocusScroll(bool* aSuppressFocusScroll)
+{
+ *aSuppressFocusScroll = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULCommandDispatcher::SetSuppressFocusScroll(bool aSuppressFocusScroll)
+{
+ return NS_OK;
+}
+
diff --git a/dom/xul/nsXULCommandDispatcher.h b/dom/xul/nsXULCommandDispatcher.h
new file mode 100644
index 0000000000..bb33edc8e0
--- /dev/null
+++ b/dom/xul/nsXULCommandDispatcher.h
@@ -0,0 +1,73 @@
+/* -*- 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 is the focus manager for XUL documents.
+
+*/
+
+#ifndef nsXULCommandDispatcher_h__
+#define nsXULCommandDispatcher_h__
+
+#include "nsCOMPtr.h"
+#include "nsIDOMXULCommandDispatcher.h"
+#include "nsWeakReference.h"
+#include "nsIDOMNode.h"
+#include "nsString.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsIDOMElement;
+class nsPIWindowRoot;
+
+class nsXULCommandDispatcher : public nsIDOMXULCommandDispatcher,
+ public nsSupportsWeakReference
+{
+public:
+ explicit nsXULCommandDispatcher(nsIDocument* aDocument);
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsXULCommandDispatcher,
+ nsIDOMXULCommandDispatcher)
+
+ // nsIDOMXULCommandDispatcher interface
+ NS_DECL_NSIDOMXULCOMMANDDISPATCHER
+
+ void Disconnect();
+protected:
+ virtual ~nsXULCommandDispatcher();
+
+ already_AddRefed<nsPIWindowRoot> GetWindowRoot();
+
+ nsIContent* GetRootFocusedContentAndWindow(nsPIDOMWindowOuter** aWindow);
+
+ nsCOMPtr<nsIDocument> mDocument;
+
+ class Updater {
+ public:
+ Updater(nsIDOMElement* aElement,
+ const nsAString& aEvents,
+ const nsAString& aTargets)
+ : mElement(aElement),
+ mEvents(aEvents),
+ mTargets(aTargets),
+ mNext(nullptr)
+ {}
+
+ nsCOMPtr<nsIDOMElement> mElement;
+ nsString mEvents;
+ nsString mTargets;
+ Updater* mNext;
+ };
+
+ Updater* mUpdaters;
+
+ bool Matches(const nsString& aList,
+ const nsAString& aElement);
+};
+
+#endif // nsXULCommandDispatcher_h__
diff --git a/dom/xul/nsXULContentSink.cpp b/dom/xul/nsXULContentSink.cpp
new file mode 100644
index 0000000000..7103be758d
--- /dev/null
+++ b/dom/xul/nsXULContentSink.cpp
@@ -0,0 +1,1051 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * An implementation for a Gecko-style content sink that knows how
+ * to build a content model (the "prototype" document) from XUL.
+ *
+ * For more information on XUL,
+ * see http://developer.mozilla.org/en/docs/XUL
+ */
+
+#include "nsXULContentSink.h"
+
+#include "jsfriendapi.h"
+
+#include "nsCOMPtr.h"
+#include "nsForwardReference.h"
+#include "nsHTMLStyleSheet.h"
+#include "nsIContentSink.h"
+#include "nsIDocument.h"
+#include "nsIDOMEventListener.h"
+#include "nsIDOMHTMLFormElement.h"
+#include "nsIDOMXULDocument.h"
+#include "nsIFormControl.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIServiceManager.h"
+#include "nsIURL.h"
+#include "nsNameSpaceManager.h"
+#include "nsParserBase.h"
+#include "nsViewManager.h"
+#include "nsIXULDocument.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsLayoutCID.h"
+#include "nsNetUtil.h"
+#include "nsRDFCID.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "nsXULElement.h"
+#include "mozilla/Logging.h"
+#include "prmem.h"
+#include "nsCRT.h"
+
+#include "nsXULPrototypeDocument.h" // XXXbe temporary
+#include "mozilla/css/Loader.h"
+
+#include "nsUnicharUtils.h"
+#include "nsGkAtoms.h"
+#include "nsContentUtils.h"
+#include "nsAttrName.h"
+#include "nsXMLContentSink.h"
+#include "nsIConsoleService.h"
+#include "nsIScriptError.h"
+#include "nsContentTypeParser.h"
+
+static mozilla::LazyLogModule gContentSinkLog("nsXULContentSink");;
+
+//----------------------------------------------------------------------
+
+XULContentSinkImpl::ContextStack::ContextStack()
+ : mTop(nullptr), mDepth(0)
+{
+}
+
+XULContentSinkImpl::ContextStack::~ContextStack()
+{
+ while (mTop) {
+ Entry* doomed = mTop;
+ mTop = mTop->mNext;
+ delete doomed;
+ }
+}
+
+nsresult
+XULContentSinkImpl::ContextStack::Push(nsXULPrototypeNode* aNode, State aState)
+{
+ Entry* entry = new Entry;
+ entry->mNode = aNode;
+ entry->mState = aState;
+ entry->mNext = mTop;
+
+ mTop = entry;
+
+ ++mDepth;
+ return NS_OK;
+}
+
+nsresult
+XULContentSinkImpl::ContextStack::Pop(State* aState)
+{
+ if (mDepth == 0)
+ return NS_ERROR_UNEXPECTED;
+
+ Entry* entry = mTop;
+ mTop = mTop->mNext;
+ --mDepth;
+
+ *aState = entry->mState;
+ delete entry;
+
+ return NS_OK;
+}
+
+
+nsresult
+XULContentSinkImpl::ContextStack::GetTopNode(RefPtr<nsXULPrototypeNode>& aNode)
+{
+ if (mDepth == 0)
+ return NS_ERROR_UNEXPECTED;
+
+ aNode = mTop->mNode;
+ return NS_OK;
+}
+
+
+nsresult
+XULContentSinkImpl::ContextStack::GetTopChildren(nsPrototypeArray** aChildren)
+{
+ if (mDepth == 0)
+ return NS_ERROR_UNEXPECTED;
+
+ *aChildren = &(mTop->mChildren);
+ return NS_OK;
+}
+
+void
+XULContentSinkImpl::ContextStack::Clear()
+{
+ Entry *cur = mTop;
+ while (cur) {
+ // Release the root element (and its descendants).
+ Entry *next = cur->mNext;
+ delete cur;
+ cur = next;
+ }
+
+ mTop = nullptr;
+ mDepth = 0;
+}
+
+void
+XULContentSinkImpl::ContextStack::Traverse(nsCycleCollectionTraversalCallback& aCb)
+{
+ nsCycleCollectionTraversalCallback& cb = aCb;
+ for (ContextStack::Entry* tmp = mTop; tmp; tmp = tmp->mNext) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNode)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildren)
+ }
+}
+
+//----------------------------------------------------------------------
+
+
+XULContentSinkImpl::XULContentSinkImpl()
+ : mText(nullptr),
+ mTextLength(0),
+ mTextSize(0),
+ mConstrainSize(true),
+ mState(eInProlog)
+{
+}
+
+
+XULContentSinkImpl::~XULContentSinkImpl()
+{
+ // The context stack _should_ be empty, unless something has gone wrong.
+ NS_ASSERTION(mContextStack.Depth() == 0, "Context stack not empty?");
+ mContextStack.Clear();
+
+ free(mText);
+}
+
+//----------------------------------------------------------------------
+// nsISupports interface
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(XULContentSinkImpl)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(XULContentSinkImpl)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNodeInfoManager)
+ tmp->mContextStack.Clear();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototype)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParser)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(XULContentSinkImpl)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNodeInfoManager)
+ tmp->mContextStack.Traverse(cb);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototype)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParser)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XULContentSinkImpl)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXMLContentSink)
+ NS_INTERFACE_MAP_ENTRY(nsIXMLContentSink)
+ NS_INTERFACE_MAP_ENTRY(nsIExpatSink)
+ NS_INTERFACE_MAP_ENTRY(nsIContentSink)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(XULContentSinkImpl)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(XULContentSinkImpl)
+
+//----------------------------------------------------------------------
+// nsIContentSink interface
+
+NS_IMETHODIMP
+XULContentSinkImpl::WillBuildModel(nsDTDMode aDTDMode)
+{
+#if FIXME
+ if (! mParentContentSink) {
+ // If we're _not_ an overlay, then notify the document that
+ // the load is beginning.
+ mDocument->BeginLoad();
+ }
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::DidBuildModel(bool aTerminated)
+{
+ nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument);
+ if (doc) {
+ doc->EndLoad();
+ mDocument = nullptr;
+ }
+
+ // Drop our reference to the parser to get rid of a circular
+ // reference.
+ mParser = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::WillInterrupt(void)
+{
+ // XXX Notify the docshell, if necessary
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::WillResume(void)
+{
+ // XXX Notify the docshell, if necessary
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::SetParser(nsParserBase* aParser)
+{
+ mParser = aParser;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::SetDocumentCharset(nsACString& aCharset)
+{
+ nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument);
+ if (doc) {
+ doc->SetDocumentCharacterSet(aCharset);
+ }
+
+ return NS_OK;
+}
+
+nsISupports *
+XULContentSinkImpl::GetTarget()
+{
+ nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument);
+ return doc;
+}
+
+//----------------------------------------------------------------------
+
+nsresult
+XULContentSinkImpl::Init(nsIDocument* aDocument,
+ nsXULPrototypeDocument* aPrototype)
+{
+ NS_PRECONDITION(aDocument != nullptr, "null ptr");
+ if (! aDocument)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ mDocument = do_GetWeakReference(aDocument);
+ mPrototype = aPrototype;
+
+ mDocumentURL = mPrototype->GetURI();
+
+ // XXX this presumes HTTP header info is already set in document
+ // XXX if it isn't we need to set it here...
+ // XXXbz not like GetHeaderData on the proto doc _does_ anything....
+ nsAutoString preferredStyle;
+ rv = mPrototype->GetHeaderData(nsGkAtoms::headerDefaultStyle,
+ preferredStyle);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!preferredStyle.IsEmpty()) {
+ aDocument->SetHeaderData(nsGkAtoms::headerDefaultStyle,
+ preferredStyle);
+ }
+
+ // Set the right preferred style on the document's CSSLoader.
+ aDocument->CSSLoader()->SetPreferredSheet(preferredStyle);
+
+ mNodeInfoManager = aPrototype->GetNodeInfoManager();
+ if (! mNodeInfoManager)
+ return NS_ERROR_UNEXPECTED;
+
+ mState = eInProlog;
+ return NS_OK;
+}
+
+
+//----------------------------------------------------------------------
+//
+// Text buffering
+//
+
+bool
+XULContentSinkImpl::IsDataInBuffer(char16_t* buffer, int32_t length)
+{
+ for (int32_t i = 0; i < length; ++i) {
+ if (buffer[i] == ' ' ||
+ buffer[i] == '\t' ||
+ buffer[i] == '\n' ||
+ buffer[i] == '\r')
+ continue;
+
+ return true;
+ }
+ return false;
+}
+
+
+nsresult
+XULContentSinkImpl::FlushText(bool aCreateTextNode)
+{
+ nsresult rv;
+
+ do {
+ // Don't do anything if there's no text to create a node from, or
+ // if they've told us not to create a text node
+ if (! mTextLength)
+ break;
+
+ if (! aCreateTextNode)
+ break;
+
+ RefPtr<nsXULPrototypeNode> node;
+ rv = mContextStack.GetTopNode(node);
+ if (NS_FAILED(rv)) return rv;
+
+ bool stripWhitespace = false;
+ if (node->mType == nsXULPrototypeNode::eType_Element) {
+ mozilla::dom::NodeInfo *nodeInfo =
+ static_cast<nsXULPrototypeElement*>(node.get())->mNodeInfo;
+
+ if (nodeInfo->NamespaceEquals(kNameSpaceID_XUL))
+ stripWhitespace = !nodeInfo->Equals(nsGkAtoms::label) &&
+ !nodeInfo->Equals(nsGkAtoms::description);
+ }
+
+ // Don't bother if there's nothing but whitespace.
+ if (stripWhitespace && ! IsDataInBuffer(mText, mTextLength))
+ break;
+
+ // Don't bother if we're not in XUL document body
+ if (mState != eInDocumentElement || mContextStack.Depth() == 0)
+ break;
+
+ nsXULPrototypeText* text = new nsXULPrototypeText();
+ text->mValue.Assign(mText, mTextLength);
+ if (stripWhitespace)
+ text->mValue.Trim(" \t\n\r");
+
+ // hook it up
+ nsPrototypeArray* children = nullptr;
+ rv = mContextStack.GetTopChildren(&children);
+ if (NS_FAILED(rv)) return rv;
+
+ // transfer ownership of 'text' to the children array
+ children->AppendElement(text);
+ } while (0);
+
+ // Reset our text buffer
+ mTextLength = 0;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+
+nsresult
+XULContentSinkImpl::NormalizeAttributeString(const char16_t *aExpatName,
+ nsAttrName &aName)
+{
+ int32_t nameSpaceID;
+ nsCOMPtr<nsIAtom> prefix, localName;
+ nsContentUtils::SplitExpatName(aExpatName, getter_AddRefs(prefix),
+ getter_AddRefs(localName), &nameSpaceID);
+
+ if (nameSpaceID == kNameSpaceID_None) {
+ aName.SetTo(localName);
+
+ return NS_OK;
+ }
+
+ RefPtr<mozilla::dom::NodeInfo> ni;
+ ni = mNodeInfoManager->GetNodeInfo(localName, prefix,
+ nameSpaceID,
+ nsIDOMNode::ATTRIBUTE_NODE);
+ aName.SetTo(ni);
+
+ return NS_OK;
+}
+
+nsresult
+XULContentSinkImpl::CreateElement(mozilla::dom::NodeInfo *aNodeInfo,
+ nsXULPrototypeElement** aResult)
+{
+ nsXULPrototypeElement* element = new nsXULPrototypeElement();
+ element->mNodeInfo = aNodeInfo;
+
+ *aResult = element;
+ return NS_OK;
+}
+
+/**** BEGIN NEW APIs ****/
+
+
+NS_IMETHODIMP
+XULContentSinkImpl::HandleStartElement(const char16_t *aName,
+ const char16_t **aAtts,
+ uint32_t aAttsCount,
+ uint32_t aLineNumber)
+{
+ // XXX Hopefully the parser will flag this before we get here. If
+ // we're in the epilog, there should be no new elements
+ NS_PRECONDITION(mState != eInEpilog, "tag in XUL doc epilog");
+ NS_PRECONDITION(aAttsCount % 2 == 0, "incorrect aAttsCount");
+ // Adjust aAttsCount so it's the actual number of attributes
+ aAttsCount /= 2;
+
+ if (mState == eInEpilog)
+ return NS_ERROR_UNEXPECTED;
+
+ if (mState != eInScript) {
+ FlushText();
+ }
+
+ int32_t nameSpaceID;
+ nsCOMPtr<nsIAtom> prefix, localName;
+ nsContentUtils::SplitExpatName(aName, getter_AddRefs(prefix),
+ getter_AddRefs(localName), &nameSpaceID);
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ nodeInfo = mNodeInfoManager->GetNodeInfo(localName, prefix, nameSpaceID,
+ nsIDOMNode::ELEMENT_NODE);
+
+ nsresult rv = NS_OK;
+ switch (mState) {
+ case eInProlog:
+ // We're the root document element
+ rv = OpenRoot(aAtts, aAttsCount, nodeInfo);
+ break;
+
+ case eInDocumentElement:
+ rv = OpenTag(aAtts, aAttsCount, aLineNumber, nodeInfo);
+ break;
+
+ case eInEpilog:
+ case eInScript:
+ MOZ_LOG(gContentSinkLog, LogLevel::Warning,
+ ("xul: warning: unexpected tags in epilog at line %d",
+ aLineNumber));
+ rv = NS_ERROR_UNEXPECTED; // XXX
+ break;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::HandleEndElement(const char16_t *aName)
+{
+ // Never EVER return anything but NS_OK or
+ // NS_ERROR_HTMLPARSER_BLOCK from this method. Doing so will blow
+ // the parser's little mind all over the planet.
+ nsresult rv;
+
+ RefPtr<nsXULPrototypeNode> node;
+ rv = mContextStack.GetTopNode(node);
+
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ switch (node->mType) {
+ case nsXULPrototypeNode::eType_Element: {
+ // Flush any text _now_, so that we'll get text nodes created
+ // before popping the stack.
+ FlushText();
+
+ // Pop the context stack and do prototype hookup.
+ nsPrototypeArray* children = nullptr;
+ rv = mContextStack.GetTopChildren(&children);
+ if (NS_FAILED(rv)) return rv;
+
+ nsXULPrototypeElement* element =
+ static_cast<nsXULPrototypeElement*>(node.get());
+
+ int32_t count = children->Length();
+ if (count) {
+ element->mChildren.SetCapacity(count);
+
+ for (int32_t i = 0; i < count; ++i)
+ element->mChildren.AppendElement(children->ElementAt(i));
+
+ }
+ }
+ break;
+
+ case nsXULPrototypeNode::eType_Script: {
+ nsXULPrototypeScript* script =
+ static_cast<nsXULPrototypeScript*>(node.get());
+
+ // If given a src= attribute, we must ignore script tag content.
+ if (!script->mSrcURI && !script->HasScriptObject()) {
+ nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument);
+
+ script->mOutOfLine = false;
+ if (doc)
+ script->Compile(mText, mTextLength, mDocumentURL,
+ script->mLineNo, doc);
+ }
+
+ FlushText(false);
+ }
+ break;
+
+ default:
+ NS_ERROR("didn't expect that");
+ break;
+ }
+
+ rv = mContextStack.Pop(&mState);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "context stack corrupted");
+ if (NS_FAILED(rv)) return rv;
+
+ if (mContextStack.Depth() == 0) {
+ // The root element should -always- be an element, because
+ // it'll have been created via XULContentSinkImpl::OpenRoot().
+ NS_ASSERTION(node->mType == nsXULPrototypeNode::eType_Element, "root is not an element");
+ if (node->mType != nsXULPrototypeNode::eType_Element)
+ return NS_ERROR_UNEXPECTED;
+
+ // Now that we're done parsing, set the prototype document's
+ // root element. This transfers ownership of the prototype
+ // element tree to the prototype document.
+ nsXULPrototypeElement* element =
+ static_cast<nsXULPrototypeElement*>(node.get());
+
+ mPrototype->SetRootElement(element);
+ mState = eInEpilog;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::HandleComment(const char16_t *aName)
+{
+ FlushText();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::HandleCDataSection(const char16_t *aData, uint32_t aLength)
+{
+ FlushText();
+ return AddText(aData, aLength);
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::HandleDoctypeDecl(const nsAString & aSubset,
+ const nsAString & aName,
+ const nsAString & aSystemId,
+ const nsAString & aPublicId,
+ nsISupports* aCatalogData)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::HandleCharacterData(const char16_t *aData,
+ uint32_t aLength)
+{
+ if (aData && mState != eInProlog && mState != eInEpilog) {
+ return AddText(aData, aLength);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+XULContentSinkImpl::HandleProcessingInstruction(const char16_t *aTarget,
+ const char16_t *aData)
+{
+ FlushText();
+
+ const nsDependentString target(aTarget);
+ const nsDependentString data(aData);
+
+ // Note: the created nsXULPrototypePI has mRefCnt == 1
+ RefPtr<nsXULPrototypePI> pi = new nsXULPrototypePI();
+ pi->mTarget = target;
+ pi->mData = data;
+
+ if (mState == eInProlog) {
+ // Note: passing in already addrefed pi
+ return mPrototype->AddProcessingInstruction(pi);
+ }
+
+ nsresult rv;
+ nsPrototypeArray* children = nullptr;
+ rv = mContextStack.GetTopChildren(&children);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!children->AppendElement(pi)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+XULContentSinkImpl::HandleXMLDeclaration(const char16_t *aVersion,
+ const char16_t *aEncoding,
+ int32_t aStandalone)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+XULContentSinkImpl::ReportError(const char16_t* aErrorText,
+ const char16_t* aSourceText,
+ nsIScriptError *aError,
+ bool *_retval)
+{
+ NS_PRECONDITION(aError && aSourceText && aErrorText, "Check arguments!!!");
+
+ // The expat driver should report the error.
+ *_retval = true;
+
+ nsresult rv = NS_OK;
+
+ // make sure to empty the context stack so that
+ // <parsererror> could become the root element.
+ mContextStack.Clear();
+
+ mState = eInProlog;
+
+ // Clear any buffered-up text we have. It's enough to set the length to 0.
+ // The buffer itself is allocated when we're created and deleted in our
+ // destructor, so don't mess with it.
+ mTextLength = 0;
+
+ // return leaving the document empty if we're asked to not add a <parsererror> root node
+ nsCOMPtr<nsIDocument> idoc = do_QueryReferent(mDocument);
+ if (idoc && idoc->SuppressParserErrorElement()) {
+ return NS_OK;
+ };
+
+ nsCOMPtr<nsIXULDocument> doc = do_QueryReferent(mDocument);
+ if (doc && !doc->OnDocumentParserError()) {
+ // The overlay was broken. Don't add a messy element to the master doc.
+ return NS_OK;
+ }
+
+ const char16_t* noAtts[] = { 0, 0 };
+
+ NS_NAMED_LITERAL_STRING(errorNs,
+ "http://www.mozilla.org/newlayout/xml/parsererror.xml");
+
+ nsAutoString parsererror(errorNs);
+ parsererror.Append((char16_t)0xFFFF);
+ parsererror.AppendLiteral("parsererror");
+
+ rv = HandleStartElement(parsererror.get(), noAtts, 0, 0);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = HandleCharacterData(aErrorText, NS_strlen(aErrorText));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoString sourcetext(errorNs);
+ sourcetext.Append((char16_t)0xFFFF);
+ sourcetext.AppendLiteral("sourcetext");
+
+ rv = HandleStartElement(sourcetext.get(), noAtts, 0, 0);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = HandleCharacterData(aSourceText, NS_strlen(aSourceText));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = HandleEndElement(sourcetext.get());
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = HandleEndElement(parsererror.get());
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return rv;
+}
+
+nsresult
+XULContentSinkImpl::OpenRoot(const char16_t** aAttributes,
+ const uint32_t aAttrLen,
+ mozilla::dom::NodeInfo *aNodeInfo)
+{
+ NS_ASSERTION(mState == eInProlog, "how'd we get here?");
+ if (mState != eInProlog)
+ return NS_ERROR_UNEXPECTED;
+
+ nsresult rv;
+
+ if (aNodeInfo->Equals(nsGkAtoms::script, kNameSpaceID_XHTML) ||
+ aNodeInfo->Equals(nsGkAtoms::script, kNameSpaceID_XUL)) {
+ MOZ_LOG(gContentSinkLog, LogLevel::Error,
+ ("xul: script tag not allowed as root content element"));
+
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Create the element
+ nsXULPrototypeElement* element;
+ rv = CreateElement(aNodeInfo, &element);
+
+ if (NS_FAILED(rv)) {
+ if (MOZ_LOG_TEST(gContentSinkLog, LogLevel::Error)) {
+ nsAutoString anodeC;
+ aNodeInfo->GetName(anodeC);
+ MOZ_LOG(gContentSinkLog, LogLevel::Error,
+ ("xul: unable to create element '%s' at line %d",
+ NS_ConvertUTF16toUTF8(anodeC).get(),
+ -1)); // XXX pass in line number
+ }
+
+ return rv;
+ }
+
+ // Push the element onto the context stack, so that child
+ // containers will hook up to us as their parent.
+ rv = mContextStack.Push(element, mState);
+ if (NS_FAILED(rv)) {
+ element->Release();
+ return rv;
+ }
+
+ // Add the attributes
+ rv = AddAttributes(aAttributes, aAttrLen, element);
+ if (NS_FAILED(rv)) return rv;
+
+ mState = eInDocumentElement;
+ return NS_OK;
+}
+
+nsresult
+XULContentSinkImpl::OpenTag(const char16_t** aAttributes,
+ const uint32_t aAttrLen,
+ const uint32_t aLineNumber,
+ mozilla::dom::NodeInfo *aNodeInfo)
+{
+ nsresult rv;
+
+ // Create the element
+ nsXULPrototypeElement* element;
+ rv = CreateElement(aNodeInfo, &element);
+
+ if (NS_FAILED(rv)) {
+ if (MOZ_LOG_TEST(gContentSinkLog, LogLevel::Error)) {
+ nsAutoString anodeC;
+ aNodeInfo->GetName(anodeC);
+ MOZ_LOG(gContentSinkLog, LogLevel::Error,
+ ("xul: unable to create element '%s' at line %d",
+ NS_ConvertUTF16toUTF8(anodeC).get(),
+ aLineNumber));
+ }
+
+ return rv;
+ }
+
+ // Link this element to its parent.
+ nsPrototypeArray* children = nullptr;
+ rv = mContextStack.GetTopChildren(&children);
+ if (NS_FAILED(rv)) {
+ delete element;
+ return rv;
+ }
+
+ // Add the attributes
+ rv = AddAttributes(aAttributes, aAttrLen, element);
+ if (NS_FAILED(rv)) return rv;
+
+ children->AppendElement(element);
+
+ if (aNodeInfo->Equals(nsGkAtoms::script, kNameSpaceID_XHTML) ||
+ aNodeInfo->Equals(nsGkAtoms::script, kNameSpaceID_XUL)) {
+ // Do scripty things now
+ rv = OpenScript(aAttributes, aLineNumber);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(mState == eInScript || mState == eInDocumentElement,
+ "Unexpected state");
+ if (mState == eInScript) {
+ // OpenScript has pushed the nsPrototypeScriptElement onto the
+ // stack, so we're done.
+ return NS_OK;
+ }
+ }
+
+ // Push the element onto the context stack, so that child
+ // containers will hook up to us as their parent.
+ rv = mContextStack.Push(element, mState);
+ if (NS_FAILED(rv)) return rv;
+
+ mState = eInDocumentElement;
+ return NS_OK;
+}
+
+nsresult
+XULContentSinkImpl::OpenScript(const char16_t** aAttributes,
+ const uint32_t aLineNumber)
+{
+ bool isJavaScript = true;
+ uint32_t version = JSVERSION_LATEST;
+ nsresult rv;
+
+ // Look for SRC attribute and look for a LANGUAGE attribute
+ nsAutoString src;
+ while (*aAttributes) {
+ const nsDependentString key(aAttributes[0]);
+ if (key.EqualsLiteral("src")) {
+ src.Assign(aAttributes[1]);
+ } else if (key.EqualsLiteral("type")) {
+ nsDependentString str(aAttributes[1]);
+ nsContentTypeParser parser(str);
+ nsAutoString mimeType;
+ rv = parser.GetType(mimeType);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_INVALID_ARG) {
+ // Fail immediately rather than checking if later things
+ // are okay.
+ return NS_OK;
+ }
+ // We do want the warning here
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (nsContentUtils::IsJavascriptMIMEType(mimeType)) {
+ isJavaScript = true;
+ version = JSVERSION_LATEST;
+
+ // Get the version string, and ensure that JavaScript supports it.
+ nsAutoString versionName;
+ rv = parser.GetParameter("version", versionName);
+
+ if (NS_SUCCEEDED(rv)) {
+ version = nsContentUtils::ParseJavascriptVersion(versionName);
+ } else if (rv != NS_ERROR_INVALID_ARG) {
+ return rv;
+ }
+ } else {
+ isJavaScript = false;
+ }
+ } else if (key.EqualsLiteral("language")) {
+ // Language is deprecated, and the impl in nsScriptLoader ignores the
+ // various version strings anyway. So we make no attempt to support
+ // languages other than JS for language=
+ nsAutoString lang(aAttributes[1]);
+ if (nsContentUtils::IsJavaScriptLanguage(lang)) {
+ isJavaScript = true;
+ version = JSVERSION_DEFAULT;
+ }
+ }
+ aAttributes += 2;
+ }
+
+ // Don't process scripts that aren't JavaScript.
+ if (!isJavaScript) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDocument> doc(do_QueryReferent(mDocument));
+ nsCOMPtr<nsIScriptGlobalObject> globalObject;
+ if (doc)
+ globalObject = do_QueryInterface(doc->GetWindow());
+ RefPtr<nsXULPrototypeScript> script =
+ new nsXULPrototypeScript(aLineNumber, version);
+
+ // If there is a SRC attribute...
+ if (! src.IsEmpty()) {
+ // Use the SRC attribute value to load the URL
+ rv = NS_NewURI(getter_AddRefs(script->mSrcURI), src, nullptr, mDocumentURL);
+
+ // Check if this document is allowed to load a script from this source
+ // NOTE: if we ever allow scripts added via the DOM to run, we need to
+ // add a CheckLoadURI call for that as well.
+ if (NS_SUCCEEDED(rv)) {
+ if (!mSecMan)
+ mSecMan = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument, &rv);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = mSecMan->
+ CheckLoadURIWithPrincipal(doc->NodePrincipal(),
+ script->mSrcURI,
+ nsIScriptSecurityManager::ALLOW_CHROME);
+ }
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Attempt to deserialize an out-of-line script from the FastLoad
+ // file right away. Otherwise we'll end up reloading the script and
+ // corrupting the FastLoad file trying to serialize it, in the case
+ // where it's already there.
+ script->DeserializeOutOfLine(nullptr, mPrototype);
+ }
+
+ nsPrototypeArray* children = nullptr;
+ rv = mContextStack.GetTopChildren(&children);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ children->AppendElement(script);
+
+ mConstrainSize = false;
+
+ mContextStack.Push(script, mState);
+ mState = eInScript;
+
+ return NS_OK;
+}
+
+nsresult
+XULContentSinkImpl::AddAttributes(const char16_t** aAttributes,
+ const uint32_t aAttrLen,
+ nsXULPrototypeElement* aElement)
+{
+ // Add tag attributes to the element
+ nsresult rv;
+
+ // Create storage for the attributes
+ nsXULPrototypeAttribute* attrs = nullptr;
+ if (aAttrLen > 0) {
+ attrs = new nsXULPrototypeAttribute[aAttrLen];
+ }
+
+ aElement->mAttributes = attrs;
+ aElement->mNumAttributes = aAttrLen;
+
+ // Copy the attributes into the prototype
+ uint32_t i;
+ for (i = 0; i < aAttrLen; ++i) {
+ rv = NormalizeAttributeString(aAttributes[i * 2], attrs[i].mName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aElement->SetAttrAt(i, nsDependentString(aAttributes[i * 2 + 1]),
+ mDocumentURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (MOZ_LOG_TEST(gContentSinkLog, LogLevel::Debug)) {
+ nsAutoString extraWhiteSpace;
+ int32_t cnt = mContextStack.Depth();
+ while (--cnt >= 0)
+ extraWhiteSpace.AppendLiteral(" ");
+ nsAutoString qnameC,valueC;
+ qnameC.Assign(aAttributes[0]);
+ valueC.Assign(aAttributes[1]);
+ MOZ_LOG(gContentSinkLog, LogLevel::Debug,
+ ("xul: %.5d. %s %s=%s",
+ -1, // XXX pass in line number
+ NS_ConvertUTF16toUTF8(extraWhiteSpace).get(),
+ NS_ConvertUTF16toUTF8(qnameC).get(),
+ NS_ConvertUTF16toUTF8(valueC).get()));
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+XULContentSinkImpl::AddText(const char16_t* aText,
+ int32_t aLength)
+{
+ // Create buffer when we first need it
+ if (0 == mTextSize) {
+ mText = (char16_t *) malloc(sizeof(char16_t) * 4096);
+ if (nullptr == mText) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mTextSize = 4096;
+ }
+
+ // Copy data from string into our buffer; flush buffer when it fills up
+ int32_t offset = 0;
+ while (0 != aLength) {
+ int32_t amount = mTextSize - mTextLength;
+ if (amount > aLength) {
+ amount = aLength;
+ }
+ if (0 == amount) {
+ if (mConstrainSize) {
+ nsresult rv = FlushText();
+ if (NS_OK != rv) {
+ return rv;
+ }
+ } else {
+ CheckedInt32 size = mTextSize;
+ size += aLength;
+ if (!size.isValid()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mTextSize = size.value();
+
+ mText = (char16_t *) realloc(mText, sizeof(char16_t) * mTextSize);
+ if (nullptr == mText) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+ memcpy(&mText[mTextLength],aText + offset, sizeof(char16_t) * amount);
+
+ mTextLength += amount;
+ offset += amount;
+ aLength -= amount;
+ }
+
+ return NS_OK;
+}
diff --git a/dom/xul/nsXULContentSink.h b/dom/xul/nsXULContentSink.h
new file mode 100644
index 0000000000..ff38cad608
--- /dev/null
+++ b/dom/xul/nsXULContentSink.h
@@ -0,0 +1,150 @@
+/* -*- 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 nsXULContentSink_h__
+#define nsXULContentSink_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIExpatSink.h"
+#include "nsIXMLContentSink.h"
+#include "nsNodeInfoManager.h"
+#include "nsWeakPtr.h"
+#include "nsXULElement.h"
+#include "nsIDTD.h"
+
+class nsIDocument;
+class nsIScriptSecurityManager;
+class nsAttrName;
+class nsXULPrototypeDocument;
+class nsXULPrototypeElement;
+class nsXULPrototypeNode;
+
+class XULContentSinkImpl final : public nsIXMLContentSink,
+ public nsIExpatSink
+{
+public:
+ XULContentSinkImpl();
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIEXPATSINK
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(XULContentSinkImpl, nsIXMLContentSink)
+
+ // nsIContentSink
+ NS_IMETHOD WillParse(void) override { return NS_OK; }
+ NS_IMETHOD WillBuildModel(nsDTDMode aDTDMode) override;
+ NS_IMETHOD DidBuildModel(bool aTerminated) override;
+ NS_IMETHOD WillInterrupt(void) override;
+ NS_IMETHOD WillResume(void) override;
+ NS_IMETHOD SetParser(nsParserBase* aParser) override;
+ virtual void FlushPendingNotifications(mozFlushType aType) override { }
+ NS_IMETHOD SetDocumentCharset(nsACString& aCharset) override;
+ virtual nsISupports *GetTarget() override;
+
+ /**
+ * Initialize the content sink, giving it an nsIDocument object
+ * with which to communicate with the outside world, and an
+ * nsXULPrototypeDocument to build.
+ */
+ nsresult Init(nsIDocument* aDocument, nsXULPrototypeDocument* aPrototype);
+
+protected:
+ virtual ~XULContentSinkImpl();
+
+ // pseudo-constants
+ char16_t* mText;
+ int32_t mTextLength;
+ int32_t mTextSize;
+ bool mConstrainSize;
+
+ nsresult AddAttributes(const char16_t** aAttributes,
+ const uint32_t aAttrLen,
+ nsXULPrototypeElement* aElement);
+
+ nsresult OpenRoot(const char16_t** aAttributes,
+ const uint32_t aAttrLen,
+ mozilla::dom::NodeInfo *aNodeInfo);
+
+ nsresult OpenTag(const char16_t** aAttributes,
+ const uint32_t aAttrLen,
+ const uint32_t aLineNumber,
+ mozilla::dom::NodeInfo *aNodeInfo);
+
+ // If OpenScript returns NS_OK and after it returns our state is eInScript,
+ // that means that we created a prototype script and stuck it on
+ // mContextStack. If NS_OK is returned but the state is still
+ // eInDocumentElement then we didn't create a prototype script (e.g. the
+ // script had an unknown type), and the caller should create a prototype
+ // element.
+ nsresult OpenScript(const char16_t** aAttributes,
+ const uint32_t aLineNumber);
+
+ static bool IsDataInBuffer(char16_t* aBuffer, int32_t aLength);
+
+ // Text management
+ nsresult FlushText(bool aCreateTextNode = true);
+ nsresult AddText(const char16_t* aText, int32_t aLength);
+
+
+ RefPtr<nsNodeInfoManager> mNodeInfoManager;
+
+ nsresult NormalizeAttributeString(const char16_t *aExpatName,
+ nsAttrName &aName);
+ nsresult CreateElement(mozilla::dom::NodeInfo *aNodeInfo,
+ nsXULPrototypeElement** aResult);
+
+
+ public:
+ enum State { eInProlog, eInDocumentElement, eInScript, eInEpilog };
+ protected:
+
+ State mState;
+
+ // content stack management
+ class ContextStack {
+ protected:
+ struct Entry {
+ RefPtr<nsXULPrototypeNode> mNode;
+ // a LOT of nodes have children; preallocate for 8
+ nsPrototypeArray mChildren;
+ State mState;
+ Entry* mNext;
+ Entry() : mChildren(8) {}
+ };
+
+ Entry* mTop;
+ int32_t mDepth;
+
+ public:
+ ContextStack();
+ ~ContextStack();
+
+ int32_t Depth() { return mDepth; }
+
+ nsresult Push(nsXULPrototypeNode* aNode, State aState);
+ nsresult Pop(State* aState);
+
+ nsresult GetTopNode(RefPtr<nsXULPrototypeNode>& aNode);
+ nsresult GetTopChildren(nsPrototypeArray** aChildren);
+
+ void Clear();
+
+ void Traverse(nsCycleCollectionTraversalCallback& aCallback);
+ };
+
+ friend class ContextStack;
+ ContextStack mContextStack;
+
+ nsWeakPtr mDocument; // [OWNER]
+ nsCOMPtr<nsIURI> mDocumentURL; // [OWNER]
+
+ RefPtr<nsXULPrototypeDocument> mPrototype; // [OWNER]
+
+ RefPtr<nsParserBase> mParser;
+ nsCOMPtr<nsIScriptSecurityManager> mSecMan;
+};
+
+#endif /* nsXULContentSink_h__ */
diff --git a/dom/xul/nsXULControllers.cpp b/dom/xul/nsXULControllers.cpp
new file mode 100644
index 0000000000..6b795b29a6
--- /dev/null
+++ b/dom/xul/nsXULControllers.cpp
@@ -0,0 +1,246 @@
+/* -*- 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 file provides the implementation for the XUL "controllers"
+ object.
+
+*/
+
+#include "nsString.h"
+
+#include "nsIControllers.h"
+#include "nsIDOMElement.h"
+#include "nsXULControllers.h"
+#include "nsDOMClassInfoID.h"
+#include "nsIController.h"
+
+//----------------------------------------------------------------------
+
+nsXULControllers::nsXULControllers()
+: mCurControllerID(0)
+{
+}
+
+nsXULControllers::~nsXULControllers(void)
+{
+ DeleteControllers();
+}
+
+void
+nsXULControllers::DeleteControllers()
+{
+ uint32_t count = mControllers.Length();
+ for (uint32_t i = 0; i < count; i++)
+ {
+ nsXULControllerData* controllerData = mControllers.ElementAt(i);
+ delete controllerData; // releases the nsIController
+ }
+
+ mControllers.Clear();
+}
+
+
+nsresult
+NS_NewXULControllers(nsISupports* aOuter, REFNSIID aIID, void** aResult)
+{
+ NS_PRECONDITION(aOuter == nullptr, "no aggregation");
+ if (aOuter)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsXULControllers* controllers = new nsXULControllers();
+ nsresult rv;
+ NS_ADDREF(controllers);
+ rv = controllers->QueryInterface(aIID, aResult);
+ NS_RELEASE(controllers);
+ return rv;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULControllers)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULControllers)
+ tmp->DeleteControllers();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULControllers)
+ {
+ uint32_t i, count = tmp->mControllers.Length();
+ for (i = 0; i < count; ++i) {
+ nsXULControllerData* controllerData = tmp->mControllers[i];
+ if (controllerData) {
+ cb.NoteXPCOMChild(controllerData->mController);
+ }
+ }
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULControllers)
+ NS_INTERFACE_MAP_ENTRY(nsIControllers)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIControllers)
+ NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(XULControllers)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULControllers)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULControllers)
+
+NS_IMETHODIMP
+nsXULControllers::GetControllerForCommand(const char *aCommand, nsIController** _retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ uint32_t count = mControllers.Length();
+ for (uint32_t i=0; i < count; i++)
+ {
+ nsXULControllerData* controllerData = mControllers.ElementAt(i);
+ if (controllerData)
+ {
+ nsCOMPtr<nsIController> controller;
+ controllerData->GetController(getter_AddRefs(controller));
+ if (controller)
+ {
+ bool supportsCommand;
+ controller->SupportsCommand(aCommand, &supportsCommand);
+ if (supportsCommand) {
+ controller.forget(_retval);
+ return NS_OK;
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULControllers::InsertControllerAt(uint32_t aIndex, nsIController *controller)
+{
+ nsXULControllerData* controllerData = new nsXULControllerData(++mCurControllerID, controller);
+#ifdef DEBUG
+ nsXULControllerData** inserted =
+#endif
+ mControllers.InsertElementAt(aIndex, controllerData);
+ NS_ASSERTION(inserted != nullptr, "Insertion of controller failed");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULControllers::RemoveControllerAt(uint32_t aIndex, nsIController **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ nsXULControllerData* controllerData = mControllers.SafeElementAt(aIndex);
+ if (!controllerData) return NS_ERROR_FAILURE;
+
+ mControllers.RemoveElementAt(aIndex);
+
+ controllerData->GetController(_retval);
+ delete controllerData;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsXULControllers::GetControllerAt(uint32_t aIndex, nsIController **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ nsXULControllerData* controllerData = mControllers.SafeElementAt(aIndex);
+ if (!controllerData) return NS_ERROR_FAILURE;
+
+ return controllerData->GetController(_retval); // does the addref
+}
+
+NS_IMETHODIMP
+nsXULControllers::AppendController(nsIController *controller)
+{
+ // This assigns controller IDs starting at 1 so we can use 0 to test if an ID was obtained
+ nsXULControllerData* controllerData = new nsXULControllerData(++mCurControllerID, controller);
+
+#ifdef DEBUG
+ nsXULControllerData** appended =
+#endif
+ mControllers.AppendElement(controllerData);
+ NS_ASSERTION(appended != nullptr, "Appending controller failed");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULControllers::RemoveController(nsIController *controller)
+{
+ // first get the identity pointer
+ nsCOMPtr<nsISupports> controllerSup(do_QueryInterface(controller));
+ // then find it
+ uint32_t count = mControllers.Length();
+ for (uint32_t i = 0; i < count; i++)
+ {
+ nsXULControllerData* controllerData = mControllers.ElementAt(i);
+ if (controllerData)
+ {
+ nsCOMPtr<nsIController> thisController;
+ controllerData->GetController(getter_AddRefs(thisController));
+ nsCOMPtr<nsISupports> thisControllerSup(do_QueryInterface(thisController)); // get identity
+ if (thisControllerSup == controllerSup)
+ {
+ mControllers.RemoveElementAt(i);
+ delete controllerData;
+ return NS_OK;
+ }
+ }
+ }
+ return NS_ERROR_FAILURE; // right thing to return if no controller found?
+}
+
+NS_IMETHODIMP
+nsXULControllers::GetControllerId(nsIController *controller, uint32_t *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ uint32_t count = mControllers.Length();
+ for (uint32_t i = 0; i < count; i++)
+ {
+ nsXULControllerData* controllerData = mControllers.ElementAt(i);
+ if (controllerData)
+ {
+ nsCOMPtr<nsIController> thisController;
+ controllerData->GetController(getter_AddRefs(thisController));
+ if (thisController.get() == controller)
+ {
+ *_retval = controllerData->GetControllerID();
+ return NS_OK;
+ }
+ }
+ }
+ return NS_ERROR_FAILURE; // none found
+}
+
+NS_IMETHODIMP
+nsXULControllers::GetControllerById(uint32_t controllerID, nsIController **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ uint32_t count = mControllers.Length();
+ for (uint32_t i = 0; i < count; i++)
+ {
+ nsXULControllerData* controllerData = mControllers.ElementAt(i);
+ if (controllerData && controllerData->GetControllerID() == controllerID)
+ {
+ return controllerData->GetController(_retval);
+ }
+ }
+ return NS_ERROR_FAILURE; // none found
+}
+
+NS_IMETHODIMP
+nsXULControllers::GetControllerCount(uint32_t *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = mControllers.Length();
+ return NS_OK;
+}
+
diff --git a/dom/xul/nsXULControllers.h b/dom/xul/nsXULControllers.h
new file mode 100644
index 0000000000..103b4aa910
--- /dev/null
+++ b/dom/xul/nsXULControllers.h
@@ -0,0 +1,71 @@
+/* -*- 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/. */
+
+/*
+
+ The XUL "controllers" object.
+
+*/
+
+#ifndef nsXULControllers_h__
+#define nsXULControllers_h__
+
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsWeakPtr.h"
+#include "nsIControllers.h"
+#include "nsCycleCollectionParticipant.h"
+
+/* non-XPCOM class for holding controllers and their IDs */
+class nsXULControllerData
+{
+public:
+ nsXULControllerData(uint32_t inControllerID, nsIController* inController)
+ : mControllerID(inControllerID)
+ , mController(inController)
+ {
+ }
+
+ ~nsXULControllerData() {}
+
+ uint32_t GetControllerID() { return mControllerID; }
+
+ nsresult GetController(nsIController **outController)
+ {
+ NS_IF_ADDREF(*outController = mController);
+ return NS_OK;
+ }
+
+ uint32_t mControllerID;
+ nsCOMPtr<nsIController> mController;
+};
+
+
+nsresult NS_NewXULControllers(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+class nsXULControllers : public nsIControllers
+{
+public:
+ friend nsresult
+ NS_NewXULControllers(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsXULControllers, nsIControllers)
+ NS_DECL_NSICONTROLLERS
+
+protected:
+ nsXULControllers();
+ virtual ~nsXULControllers(void);
+
+ void DeleteControllers();
+
+ nsTArray<nsXULControllerData*> mControllers;
+ uint32_t mCurControllerID;
+};
+
+
+
+
+#endif // nsXULControllers_h__
diff --git a/dom/xul/nsXULElement.cpp b/dom/xul/nsXULElement.cpp
new file mode 100644
index 0000000000..14fa898ab5
--- /dev/null
+++ b/dom/xul/nsXULElement.cpp
@@ -0,0 +1,2975 @@
+/* -*- 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
+ */
+
+#include "nsCOMPtr.h"
+#include "nsDOMCID.h"
+#include "nsError.h"
+#include "nsDOMString.h"
+#include "nsIDOMEvent.h"
+#include "nsIAtom.h"
+#include "nsIBaseWindow.h"
+#include "nsIDOMAttr.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMEventListener.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMXULCommandDispatcher.h"
+#include "nsIDOMXULElement.h"
+#include "nsIDOMXULSelectCntrlItemEl.h"
+#include "nsIDocument.h"
+#include "nsLayoutStylesheetCache.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/DeclarationBlockInlines.h"
+#include "nsFocusManager.h"
+#include "nsHTMLStyleSheet.h"
+#include "nsNameSpaceManager.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIPresShell.h"
+#include "nsIPrincipal.h"
+#include "nsIRDFCompositeDataSource.h"
+#include "nsIRDFNode.h"
+#include "nsIRDFService.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptError.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIServiceManager.h"
+#include "mozilla/css/StyleRule.h"
+#include "nsIURL.h"
+#include "nsViewManager.h"
+#include "nsIWidget.h"
+#include "nsIXULDocument.h"
+#include "nsIXULTemplateBuilder.h"
+#include "nsLayoutCID.h"
+#include "nsContentCID.h"
+#include "mozilla/dom/Event.h"
+#include "nsRDFCID.h"
+#include "nsStyleConsts.h"
+#include "nsXPIDLString.h"
+#include "nsXULControllers.h"
+#include "nsIBoxObject.h"
+#include "nsPIBoxObject.h"
+#include "XULDocument.h"
+#include "nsXULPopupListener.h"
+#include "nsRuleWalker.h"
+#include "nsIDOMCSSStyleDeclaration.h"
+#include "nsCSSParser.h"
+#include "ListBoxObject.h"
+#include "nsContentUtils.h"
+#include "nsContentList.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/MouseEvents.h"
+#include "nsIDOMMutationEvent.h"
+#include "nsPIDOMWindow.h"
+#include "nsJSPrincipals.h"
+#include "nsDOMAttributeMap.h"
+#include "nsGkAtoms.h"
+#include "nsXULContentUtils.h"
+#include "nsNodeUtils.h"
+#include "nsFrameLoader.h"
+#include "mozilla/Logging.h"
+#include "rdf.h"
+#include "nsIControllers.h"
+#include "nsAttrValueOrString.h"
+#include "nsAttrValueInlines.h"
+#include "mozilla/Attributes.h"
+#include "nsIController.h"
+#include "nsQueryObject.h"
+#include <algorithm>
+#include "nsIDOMChromeWindow.h"
+
+// The XUL doc interface
+#include "nsIDOMXULDocument.h"
+
+#include "nsReadableUtils.h"
+#include "nsIFrame.h"
+#include "nsNodeInfoManager.h"
+#include "nsXBLBinding.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozAutoDocUpdate.h"
+#include "nsIDOMXULCommandEvent.h"
+#include "nsCCUncollectableMarker.h"
+#include "nsICSSDeclaration.h"
+
+#include "mozilla/dom/XULElementBinding.h"
+#include "mozilla/dom/BoxObject.h"
+#include "mozilla/dom/HTMLIFrameElement.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#ifdef XUL_PROTOTYPE_ATTRIBUTE_METERING
+uint32_t nsXULPrototypeAttribute::gNumElements;
+uint32_t nsXULPrototypeAttribute::gNumAttributes;
+uint32_t nsXULPrototypeAttribute::gNumCacheTests;
+uint32_t nsXULPrototypeAttribute::gNumCacheHits;
+uint32_t nsXULPrototypeAttribute::gNumCacheSets;
+uint32_t nsXULPrototypeAttribute::gNumCacheFills;
+#endif
+
+class nsXULElementTearoff final : public nsIFrameLoaderOwner
+{
+ ~nsXULElementTearoff() {}
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsXULElementTearoff)
+
+ explicit nsXULElementTearoff(nsXULElement* aElement)
+ : mElement(aElement)
+ {
+ }
+
+ NS_FORWARD_NSIFRAMELOADEROWNER(static_cast<nsXULElement*>(mElement.get())->)
+private:
+ nsCOMPtr<nsIDOMXULElement> mElement;
+};
+
+NS_IMPL_CYCLE_COLLECTION(nsXULElementTearoff, mElement)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULElementTearoff)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULElementTearoff)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULElementTearoff)
+ NS_INTERFACE_MAP_ENTRY(nsIFrameLoaderOwner)
+NS_INTERFACE_MAP_END_AGGREGATED(mElement)
+
+//----------------------------------------------------------------------
+// nsXULElement
+//
+
+nsXULElement::nsXULElement(already_AddRefed<mozilla::dom::NodeInfo> aNodeInfo)
+ : nsStyledElement(aNodeInfo),
+ mBindingParent(nullptr)
+{
+ XUL_PROTOTYPE_ATTRIBUTE_METER(gNumElements);
+
+ // We may be READWRITE by default; check.
+ if (IsReadWriteTextElement()) {
+ AddStatesSilently(NS_EVENT_STATE_MOZ_READWRITE);
+ RemoveStatesSilently(NS_EVENT_STATE_MOZ_READONLY);
+ }
+}
+
+nsXULElement::~nsXULElement()
+{
+}
+
+nsXULElement::nsXULSlots::nsXULSlots()
+ : nsXULElement::nsDOMSlots()
+{
+}
+
+nsXULElement::nsXULSlots::~nsXULSlots()
+{
+ NS_IF_RELEASE(mControllers); // Forces release
+ nsCOMPtr<nsIFrameLoader> frameLoader = do_QueryInterface(mFrameLoaderOrOpener);
+ if (frameLoader) {
+ static_cast<nsFrameLoader*>(frameLoader.get())->Destroy();
+ }
+}
+
+void
+nsXULElement::nsXULSlots::Traverse(nsCycleCollectionTraversalCallback &cb)
+{
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mFrameLoaderOrOpener");
+ cb.NoteXPCOMChild(mFrameLoaderOrOpener);
+}
+
+nsINode::nsSlots*
+nsXULElement::CreateSlots()
+{
+ return new nsXULSlots();
+}
+
+void
+nsXULElement::MaybeUpdatePrivateLifetime()
+{
+ if (AttrValueIs(kNameSpaceID_None, nsGkAtoms::windowtype,
+ NS_LITERAL_STRING("navigator:browser"),
+ eCaseMatters)) {
+ return;
+ }
+
+ nsPIDOMWindowOuter* win = OwnerDoc()->GetWindow();
+ nsCOMPtr<nsIDocShell> docShell = win ? win->GetDocShell() : nullptr;
+ if (docShell) {
+ docShell->SetAffectPrivateSessionLifetime(false);
+ }
+}
+
+/* static */
+already_AddRefed<nsXULElement>
+nsXULElement::Create(nsXULPrototypeElement* aPrototype, mozilla::dom::NodeInfo *aNodeInfo,
+ bool aIsScriptable, bool aIsRoot)
+{
+ RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
+ RefPtr<nsXULElement> element = new nsXULElement(ni.forget());
+ if (element) {
+ if (aPrototype->mHasIdAttribute) {
+ element->SetHasID();
+ }
+ if (aPrototype->mHasClassAttribute) {
+ element->SetFlags(NODE_MAY_HAVE_CLASS);
+ }
+ if (aPrototype->mHasStyleAttribute) {
+ element->SetMayHaveStyle();
+ }
+
+ element->MakeHeavyweight(aPrototype);
+ if (aIsScriptable) {
+ // Check each attribute on the prototype to see if we need to do
+ // any additional processing and hookup that would otherwise be
+ // done 'automagically' by SetAttr().
+ for (uint32_t i = 0; i < aPrototype->mNumAttributes; ++i) {
+ element->AddListenerFor(aPrototype->mAttributes[i].mName,
+ true);
+ }
+ }
+
+ if (aIsRoot && aPrototype->mNodeInfo->Equals(nsGkAtoms::window)) {
+ for (uint32_t i = 0; i < aPrototype->mNumAttributes; ++i) {
+ if (aPrototype->mAttributes[i].mName.Equals(nsGkAtoms::windowtype)) {
+ element->MaybeUpdatePrivateLifetime();
+ }
+ }
+ }
+ }
+
+ return element.forget();
+}
+
+nsresult
+nsXULElement::Create(nsXULPrototypeElement* aPrototype,
+ nsIDocument* aDocument,
+ bool aIsScriptable,
+ bool aIsRoot,
+ Element** aResult)
+{
+ // Create an nsXULElement from a prototype
+ NS_PRECONDITION(aPrototype != nullptr, "null ptr");
+ if (! aPrototype)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ if (aDocument) {
+ mozilla::dom::NodeInfo* ni = aPrototype->mNodeInfo;
+ nodeInfo = aDocument->NodeInfoManager()->
+ GetNodeInfo(ni->NameAtom(), ni->GetPrefixAtom(), ni->NamespaceID(),
+ nsIDOMNode::ELEMENT_NODE);
+ } else {
+ nodeInfo = aPrototype->mNodeInfo;
+ }
+
+ RefPtr<nsXULElement> element = Create(aPrototype, nodeInfo,
+ aIsScriptable, aIsRoot);
+ element.forget(aResult);
+
+ return NS_OK;
+}
+
+nsresult
+NS_NewXULElement(Element** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+{
+ RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
+
+ NS_PRECONDITION(ni, "need nodeinfo for non-proto Create");
+
+ nsIDocument* doc = ni->GetDocument();
+ if (doc && !doc->AllowXULXBL()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_ADDREF(*aResult = new nsXULElement(ni.forget()));
+
+ return NS_OK;
+}
+
+void
+NS_TrustedNewXULElement(nsIContent** aResult,
+ already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+{
+ RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
+ NS_PRECONDITION(ni, "need nodeinfo for non-proto Create");
+
+ // Create an nsXULElement with the specified namespace and tag.
+ NS_ADDREF(*aResult = new nsXULElement(ni.forget()));
+}
+
+//----------------------------------------------------------------------
+// nsISupports interface
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsXULElement,
+ nsStyledElement)
+ {
+ nsXULSlots* slots = static_cast<nsXULSlots*>(tmp->GetExistingSlots());
+ if (slots) {
+ slots->Traverse(cb);
+ }
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsXULElement,
+ nsStyledElement)
+ // Why aren't we unlinking the prototype?
+ tmp->ClearHasID();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(nsXULElement, nsStyledElement)
+NS_IMPL_RELEASE_INHERITED(nsXULElement, nsStyledElement)
+
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsXULElement)
+ NS_INTERFACE_TABLE_INHERITED(nsXULElement, nsIDOMNode, nsIDOMElement,
+ nsIDOMXULElement)
+ NS_ELEMENT_INTERFACE_TABLE_TO_MAP_SEGUE
+ NS_INTERFACE_MAP_ENTRY_TEAROFF(nsIFrameLoaderOwner,
+ new nsXULElementTearoff(this))
+NS_INTERFACE_MAP_END_INHERITING(nsStyledElement)
+
+//----------------------------------------------------------------------
+// nsIDOMNode interface
+
+nsresult
+nsXULElement::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const
+{
+ *aResult = nullptr;
+
+ RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
+ RefPtr<nsXULElement> element = new nsXULElement(ni.forget());
+
+ // XXX TODO: set up RDF generic builder n' stuff if there is a
+ // 'datasources' attribute? This is really kind of tricky,
+ // because then we'd need to -selectively- copy children that
+ // -weren't- generated from RDF. Ugh. Forget it.
+
+ // Note that we're _not_ copying mControllers.
+
+ uint32_t count = mAttrsAndChildren.AttrCount();
+ nsresult rv = NS_OK;
+ for (uint32_t i = 0; i < count; ++i) {
+ const nsAttrName* originalName = mAttrsAndChildren.AttrNameAt(i);
+ const nsAttrValue* originalValue = mAttrsAndChildren.AttrAt(i);
+ nsAttrValue attrValue;
+
+ // Style rules need to be cloned.
+ if (originalValue->Type() == nsAttrValue::eCSSDeclaration) {
+ DeclarationBlock* decl = originalValue->GetCSSDeclarationValue();
+ RefPtr<css::Declaration>
+ declClone = new css::Declaration(*decl->AsGecko());
+
+ nsString stringValue;
+ originalValue->ToString(stringValue);
+
+ attrValue.SetTo(declClone.forget(), &stringValue);
+ } else {
+ attrValue.SetTo(*originalValue);
+ }
+
+ if (originalName->IsAtom()) {
+ rv = element->mAttrsAndChildren.SetAndSwapAttr(originalName->Atom(),
+ attrValue);
+ } else {
+ rv = element->mAttrsAndChildren.SetAndSwapAttr(originalName->NodeInfo(),
+ attrValue);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ element->AddListenerFor(*originalName, true);
+ if (originalName->Equals(nsGkAtoms::id) &&
+ !originalValue->IsEmptyString()) {
+ element->SetHasID();
+ }
+ if (originalName->Equals(nsGkAtoms::_class)) {
+ element->SetFlags(NODE_MAY_HAVE_CLASS);
+ }
+ if (originalName->Equals(nsGkAtoms::style)) {
+ element->SetMayHaveStyle();
+ }
+ }
+
+ element.forget(aResult);
+ return rv;
+}
+
+//----------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsXULElement::GetElementsByAttribute(const nsAString& aAttribute,
+ const nsAString& aValue,
+ nsIDOMNodeList** aReturn)
+{
+ *aReturn = GetElementsByAttribute(aAttribute, aValue).take();
+ return NS_OK;
+}
+
+already_AddRefed<nsINodeList>
+nsXULElement::GetElementsByAttribute(const nsAString& aAttribute,
+ const nsAString& aValue)
+{
+ nsCOMPtr<nsIAtom> attrAtom(NS_Atomize(aAttribute));
+ void* attrValue = new nsString(aValue);
+ RefPtr<nsContentList> list =
+ new nsContentList(this,
+ XULDocument::MatchAttribute,
+ nsContentUtils::DestroyMatchString,
+ attrValue,
+ true,
+ attrAtom,
+ kNameSpaceID_Unknown);
+ return list.forget();
+}
+
+NS_IMETHODIMP
+nsXULElement::GetElementsByAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aAttribute,
+ const nsAString& aValue,
+ nsIDOMNodeList** aReturn)
+{
+ ErrorResult rv;
+ *aReturn =
+ GetElementsByAttributeNS(aNamespaceURI, aAttribute, aValue, rv).take();
+ return rv.StealNSResult();
+}
+
+already_AddRefed<nsINodeList>
+nsXULElement::GetElementsByAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aAttribute,
+ const nsAString& aValue,
+ ErrorResult& rv)
+{
+ nsCOMPtr<nsIAtom> attrAtom(NS_Atomize(aAttribute));
+
+ int32_t nameSpaceId = kNameSpaceID_Wildcard;
+ if (!aNamespaceURI.EqualsLiteral("*")) {
+ rv =
+ nsContentUtils::NameSpaceManager()->RegisterNameSpace(aNamespaceURI,
+ nameSpaceId);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ }
+
+ void* attrValue = new nsString(aValue);
+ RefPtr<nsContentList> list =
+ new nsContentList(this,
+ XULDocument::MatchAttribute,
+ nsContentUtils::DestroyMatchString,
+ attrValue,
+ true,
+ attrAtom,
+ nameSpaceId);
+
+ return list.forget();
+}
+
+EventListenerManager*
+nsXULElement::GetEventListenerManagerForAttr(nsIAtom* aAttrName, bool* aDefer)
+{
+ // XXXbz sXBL/XBL2 issue: should we instead use GetComposedDoc()
+ // here, override BindToTree for those classes and munge event
+ // listeners there?
+ nsIDocument* doc = OwnerDoc();
+
+ nsPIDOMWindowInner *window;
+ Element *root = doc->GetRootElement();
+ if ((!root || root == this) && !mNodeInfo->Equals(nsGkAtoms::overlay) &&
+ (window = doc->GetInnerWindow())) {
+
+ nsCOMPtr<EventTarget> piTarget = do_QueryInterface(window);
+
+ *aDefer = false;
+ return piTarget->GetOrCreateListenerManager();
+ }
+
+ return nsStyledElement::GetEventListenerManagerForAttr(aAttrName, aDefer);
+}
+
+// returns true if the element is not a list
+static bool IsNonList(mozilla::dom::NodeInfo* aNodeInfo)
+{
+ return !aNodeInfo->Equals(nsGkAtoms::tree) &&
+ !aNodeInfo->Equals(nsGkAtoms::listbox) &&
+ !aNodeInfo->Equals(nsGkAtoms::richlistbox);
+}
+
+bool
+nsXULElement::IsFocusableInternal(int32_t *aTabIndex, bool aWithMouse)
+{
+ /*
+ * Returns true if an element may be focused, and false otherwise. The inout
+ * argument aTabIndex will be set to the tab order index to be used; -1 for
+ * elements that should not be part of the tab order and a greater value to
+ * indicate its tab order.
+ *
+ * Confusingly, the supplied value for the aTabIndex argument may indicate
+ * whether the element may be focused as a result of the -moz-user-focus
+ * property, where -1 means no and 0 means yes.
+ *
+ * For controls, the element cannot be focused and is not part of the tab
+ * order if it is disabled.
+ *
+ * Controls (those that implement nsIDOMXULControlElement):
+ * *aTabIndex = -1 no tabindex Not focusable or tabbable
+ * *aTabIndex = -1 tabindex="-1" Not focusable or tabbable
+ * *aTabIndex = -1 tabindex=">=0" Focusable and tabbable
+ * *aTabIndex >= 0 no tabindex Focusable and tabbable
+ * *aTabIndex >= 0 tabindex="-1" Focusable but not tabbable
+ * *aTabIndex >= 0 tabindex=">=0" Focusable and tabbable
+ * Non-controls:
+ * *aTabIndex = -1 Not focusable or tabbable
+ * *aTabIndex >= 0 Focusable and tabbable
+ *
+ * If aTabIndex is null, then the tabindex is not computed, and
+ * true is returned for non-disabled controls and false otherwise.
+ */
+
+ // elements are not focusable by default
+ bool shouldFocus = false;
+
+#ifdef XP_MACOSX
+ // on Mac, mouse interactions only focus the element if it's a list,
+ // or if it's a remote target, since the remote target must handle
+ // the focus.
+ if (aWithMouse &&
+ IsNonList(mNodeInfo) &&
+ !EventStateManager::IsRemoteTarget(this))
+ {
+ return false;
+ }
+#endif
+
+ nsCOMPtr<nsIDOMXULControlElement> xulControl = do_QueryObject(this);
+ if (xulControl) {
+ // a disabled element cannot be focused and is not part of the tab order
+ bool disabled;
+ xulControl->GetDisabled(&disabled);
+ if (disabled) {
+ if (aTabIndex)
+ *aTabIndex = -1;
+ return false;
+ }
+ shouldFocus = true;
+ }
+
+ if (aTabIndex) {
+ if (xulControl) {
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) {
+ // if either the aTabIndex argument or a specified tabindex is non-negative,
+ // the element becomes focusable.
+ int32_t tabIndex = 0;
+ xulControl->GetTabIndex(&tabIndex);
+ shouldFocus = *aTabIndex >= 0 || tabIndex >= 0;
+ *aTabIndex = tabIndex;
+ } else {
+ // otherwise, if there is no tabindex attribute, just use the value of
+ // *aTabIndex to indicate focusability. Reset any supplied tabindex to 0.
+ shouldFocus = *aTabIndex >= 0;
+ if (shouldFocus)
+ *aTabIndex = 0;
+ }
+
+ if (shouldFocus && sTabFocusModelAppliesToXUL &&
+ !(sTabFocusModel & eTabFocus_formElementsMask)) {
+ // By default, the tab focus model doesn't apply to xul element on any system but OS X.
+ // on OS X we're following it for UI elements (XUL) as sTabFocusModel is based on
+ // "Full Keyboard Access" system setting (see mac/nsILookAndFeel).
+ // both textboxes and list elements (i.e. trees and list) should always be focusable
+ // (textboxes are handled as html:input)
+ // For compatibility, we only do this for controls, otherwise elements like <browser>
+ // cannot take this focus.
+ if (IsNonList(mNodeInfo))
+ *aTabIndex = -1;
+ }
+ } else {
+ shouldFocus = *aTabIndex >= 0;
+ }
+ }
+
+ return shouldFocus;
+}
+
+bool
+nsXULElement::PerformAccesskey(bool aKeyCausesActivation,
+ bool aIsTrustedEvent)
+{
+ nsCOMPtr<nsIContent> content(this);
+
+ if (IsXULElement(nsGkAtoms::label)) {
+ nsCOMPtr<nsIDOMElement> element;
+
+ nsAutoString control;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::control, control);
+ if (!control.IsEmpty()) {
+ //XXXsmaug Should we use ShadowRoot::GetElementById in case
+ // content is in Shadow DOM?
+ nsCOMPtr<nsIDOMDocument> domDocument =
+ do_QueryInterface(content->GetUncomposedDoc());
+ if (domDocument)
+ domDocument->GetElementById(control, getter_AddRefs(element));
+ }
+ // here we'll either change |content| to the element referenced by
+ // |element|, or clear it.
+ content = do_QueryInterface(element);
+
+ if (!content) {
+ return false;
+ }
+ }
+
+ nsIFrame* frame = content->GetPrimaryFrame();
+ if (!frame || !frame->IsVisibleConsideringAncestors()) {
+ return false;
+ }
+
+ bool focused = false;
+ nsXULElement* elm = FromContent(content);
+ if (elm) {
+ // Define behavior for each type of XUL element.
+ if (!content->IsXULElement(nsGkAtoms::toolbarbutton)) {
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ nsCOMPtr<nsIDOMElement> elementToFocus;
+ // for radio buttons, focus the radiogroup instead
+ if (content->IsXULElement(nsGkAtoms::radio)) {
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> controlItem(do_QueryInterface(content));
+ if (controlItem) {
+ bool disabled;
+ controlItem->GetDisabled(&disabled);
+ if (!disabled) {
+ nsCOMPtr<nsIDOMXULSelectControlElement> selectControl;
+ controlItem->GetControl(getter_AddRefs(selectControl));
+ elementToFocus = do_QueryInterface(selectControl);
+ }
+ }
+ } else {
+ elementToFocus = do_QueryInterface(content);
+ }
+ if (elementToFocus) {
+ fm->SetFocus(elementToFocus, nsIFocusManager::FLAG_BYKEY);
+
+ // Return true if the element became focused.
+ nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow();
+ focused = (window && window->GetFocusedNode());
+ }
+ }
+ }
+ if (aKeyCausesActivation &&
+ !content->IsAnyOfXULElements(nsGkAtoms::textbox, nsGkAtoms::menulist)) {
+ elm->ClickWithInputSource(nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD, aIsTrustedEvent);
+ }
+ } else {
+ return content->PerformAccesskey(aKeyCausesActivation, aIsTrustedEvent);
+ }
+
+ return focused;
+}
+
+//----------------------------------------------------------------------
+
+void
+nsXULElement::AddListenerFor(const nsAttrName& aName,
+ bool aCompileEventHandlers)
+{
+ // If appropriate, add a popup listener and/or compile the event
+ // handler. Called when we change the element's document, create a
+ // new element, change an attribute's value, etc.
+ // Eventlistenener-attributes are always in the null namespace
+ if (aName.IsAtom()) {
+ nsIAtom *attr = aName.Atom();
+ MaybeAddPopupListener(attr);
+ if (aCompileEventHandlers &&
+ nsContentUtils::IsEventAttributeName(attr, EventNameType_XUL)) {
+ nsAutoString value;
+ GetAttr(kNameSpaceID_None, attr, value);
+ SetEventHandler(attr, value, true);
+ }
+ }
+}
+
+void
+nsXULElement::MaybeAddPopupListener(nsIAtom* aLocalName)
+{
+ // If appropriate, add a popup listener. Called when we change the
+ // element's document, create a new element, change an attribute's
+ // value, etc.
+ if (aLocalName == nsGkAtoms::menu ||
+ aLocalName == nsGkAtoms::contextmenu ||
+ // XXXdwh popup and context are deprecated
+ aLocalName == nsGkAtoms::popup ||
+ aLocalName == nsGkAtoms::context) {
+ AddPopupListener(aLocalName);
+ }
+}
+
+//----------------------------------------------------------------------
+//
+// nsIContent interface
+//
+void
+nsXULElement::UpdateEditableState(bool aNotify)
+{
+ // Don't call through to Element here because the things
+ // it does don't work for cases when we're an editable control.
+ nsIContent *parent = GetParent();
+
+ SetEditableFlag(parent && parent->HasFlag(NODE_IS_EDITABLE));
+ UpdateState(aNotify);
+}
+
+/**
+ * Returns true if the user-agent style sheet rules for this XUL element are
+ * in minimal-xul.css instead of xul.css.
+ */
+static inline bool XULElementsRulesInMinimalXULSheet(nsIAtom* aTag)
+{
+ return // scrollbar parts:
+ aTag == nsGkAtoms::scrollbar ||
+ aTag == nsGkAtoms::scrollbarbutton ||
+ aTag == nsGkAtoms::scrollcorner ||
+ aTag == nsGkAtoms::slider ||
+ aTag == nsGkAtoms::thumb ||
+ aTag == nsGkAtoms::scale ||
+ // other
+ aTag == nsGkAtoms::resizer ||
+ aTag == nsGkAtoms::label ||
+ aTag == nsGkAtoms::videocontrols;
+}
+
+#ifdef DEBUG
+/**
+ * Returns true if aElement is a XUL element created by the video controls
+ * binding. HTML <video> and <audio> bindings pull in this binding. This
+ * binding creates lots of different types of XUL elements.
+ */
+static inline bool
+IsInVideoControls(nsXULElement* aElement)
+{
+ nsIContent* ancestor = aElement->GetParent();
+ while (ancestor) {
+ if (ancestor->NodeInfo()->Equals(nsGkAtoms::videocontrols, kNameSpaceID_XUL)) {
+ return true;
+ }
+ ancestor = ancestor->GetParent();
+ }
+ return false;
+}
+
+/**
+ * Returns true if aElement is an element created by the <binding
+ * id="feedreaderUI"> binding or one of the bindings bound to such an element.
+ * element in one of the binding for such an element. Only
+ * subscribe.xhtml#feedSubscribeLine pulls in the feedreaderUI binding. This
+ * binding creates lots of different types of XUL elements.
+ */
+bool
+IsInFeedSubscribeLine(nsXULElement* aElement)
+{
+ nsIContent* bindingParent = aElement->GetBindingParent();
+ if (bindingParent) {
+ while (bindingParent->GetBindingParent()) {
+ bindingParent = bindingParent->GetBindingParent();
+ }
+ nsIAtom* idAtom = bindingParent->GetID();
+ if (idAtom && idAtom->Equals(NS_LITERAL_STRING("feedSubscribeLine"))) {
+ return true;
+ }
+ }
+ return false;
+}
+#endif
+
+class XULInContentErrorReporter : public Runnable
+{
+public:
+ explicit XULInContentErrorReporter(nsIDocument* aDocument) : mDocument(aDocument) {}
+
+ NS_IMETHOD Run() override
+ {
+ mDocument->WarnOnceAbout(nsIDocument::eImportXULIntoContent, false);
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIDocument> mDocument;
+};
+
+nsresult
+nsXULElement::BindToTree(nsIDocument* aDocument,
+ nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ if (!aBindingParent &&
+ aDocument &&
+ !aDocument->IsLoadedAsInteractiveData() &&
+ !aDocument->AllowXULXBL() &&
+ !aDocument->HasWarnedAbout(nsIDocument::eImportXULIntoContent)) {
+ nsContentUtils::AddScriptRunner(new XULInContentErrorReporter(aDocument));
+ }
+
+ nsresult rv = nsStyledElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIDocument* doc = GetComposedDoc();
+ if (doc &&
+ !doc->LoadsFullXULStyleSheetUpFront() &&
+ !doc->IsUnstyledDocument()) {
+
+ // To save CPU cycles and memory, non-XUL documents only load the user
+ // agent style sheet rules for a minimal set of XUL elements such as
+ // 'scrollbar' that may be created implicitly for their content (those
+ // rules being in minimal-xul.css). This is where we make sure that all
+ // the other XUL UA style sheet rules (xul.css) have been loaded if the
+ // minimal set is not sufficient.
+ //
+ // We do this during binding, not element construction, because elements
+ // can be moved from the document that creates them to another document.
+
+ if (!XULElementsRulesInMinimalXULSheet(NodeInfo()->NameAtom())) {
+ auto cache = nsLayoutStylesheetCache::For(doc->GetStyleBackendType());
+ doc->EnsureOnDemandBuiltInUASheet(cache->XULSheet());
+ // To keep memory usage down it is important that we try and avoid
+ // pulling xul.css into non-XUL documents. That should be very rare, and
+ // for HTML we currently should only pull it in if the document contains
+ // an <audio> or <video> element. This assertion is here to make sure
+ // that we don't fail to notice if a change to bindings causes us to
+ // start pulling in xul.css much more frequently. If this assertion
+ // fails then we need to figure out why, and how we can continue to avoid
+ // pulling in xul.css.
+ // Note that add-ons may introduce bindings that cause this assertion to
+ // fire.
+ NS_ASSERTION(IsInVideoControls(this) ||
+ IsInFeedSubscribeLine(this) ||
+ IsXULElement(nsGkAtoms::datetimebox),
+ "Unexpected XUL element in non-XUL doc");
+ }
+ }
+
+ if (aDocument) {
+ NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+ "Missing a script blocker!");
+ // We're in a document now. Kick off the frame load.
+ LoadSrc();
+ }
+
+ return rv;
+}
+
+void
+nsXULElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ // mControllers can own objects that are implemented
+ // in JavaScript (such as some implementations of
+ // nsIControllers. These objects prevent their global
+ // object's script object from being garbage collected,
+ // which means JS continues to hold an owning reference
+ // to the nsGlobalWindow, which owns the document,
+ // which owns this content. That's a cycle, so we break
+ // it here. (It might be better to break this by releasing
+ // mDocument in nsGlobalWindow::SetDocShell, but I'm not
+ // sure whether that would fix all possible cycles through
+ // mControllers.)
+ nsXULSlots* slots = static_cast<nsXULSlots*>(GetExistingDOMSlots());
+ if (slots) {
+ NS_IF_RELEASE(slots->mControllers);
+ RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
+ if (frameLoader) {
+ frameLoader->Destroy();
+ }
+ slots->mFrameLoaderOrOpener = nullptr;
+ }
+
+ nsStyledElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+void
+nsXULElement::RemoveChildAt(uint32_t aIndex, bool aNotify)
+{
+ nsCOMPtr<nsIContent> oldKid = mAttrsAndChildren.GetSafeChildAt(aIndex);
+ if (!oldKid) {
+ return;
+ }
+
+ // On the removal of a <treeitem>, <treechildren>, or <treecell> element,
+ // the possibility exists that some of the items in the removed subtree
+ // are selected (and therefore need to be deselected). We need to account for this.
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> controlElement;
+ nsCOMPtr<nsIListBoxObject> listBox;
+ bool fireSelectionHandler = false;
+
+ // -1 = do nothing, -2 = null out current item
+ // anything else = index to re-set as current
+ int32_t newCurrentIndex = -1;
+
+ if (oldKid->NodeInfo()->Equals(nsGkAtoms::listitem, kNameSpaceID_XUL)) {
+ // This is the nasty case. We have (potentially) a slew of selected items
+ // and cells going away.
+ // First, retrieve the tree.
+ // Check first whether this element IS the tree
+ controlElement = do_QueryObject(this);
+
+ // If it's not, look at our parent
+ if (!controlElement)
+ GetParentTree(getter_AddRefs(controlElement));
+ nsCOMPtr<nsIDOMXULElement> xulElement(do_QueryInterface(controlElement));
+
+ nsCOMPtr<nsIDOMElement> oldKidElem = do_QueryInterface(oldKid);
+ if (xulElement && oldKidElem) {
+ // Iterate over all of the items and find out if they are contained inside
+ // the removed subtree.
+ int32_t length;
+ controlElement->GetSelectedCount(&length);
+ for (int32_t i = 0; i < length; i++) {
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> node;
+ controlElement->MultiGetSelectedItem(i, getter_AddRefs(node));
+ // we need to QI here to do an XPCOM-correct pointercompare
+ nsCOMPtr<nsIDOMElement> selElem = do_QueryInterface(node);
+ if (selElem == oldKidElem &&
+ NS_SUCCEEDED(controlElement->RemoveItemFromSelection(node))) {
+ length--;
+ i--;
+ fireSelectionHandler = true;
+ }
+ }
+
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> curItem;
+ controlElement->GetCurrentItem(getter_AddRefs(curItem));
+ nsCOMPtr<nsIContent> curNode = do_QueryInterface(curItem);
+ if (curNode && nsContentUtils::ContentIsDescendantOf(curNode, oldKid)) {
+ // Current item going away
+ nsCOMPtr<nsIBoxObject> box;
+ xulElement->GetBoxObject(getter_AddRefs(box));
+ listBox = do_QueryInterface(box);
+ if (listBox && oldKidElem) {
+ listBox->GetIndexOfItem(oldKidElem, &newCurrentIndex);
+ }
+
+ // If any of this fails, we'll just set the current item to null
+ if (newCurrentIndex == -1)
+ newCurrentIndex = -2;
+ }
+ }
+ }
+
+ nsStyledElement::RemoveChildAt(aIndex, aNotify);
+
+ if (newCurrentIndex == -2) {
+ controlElement->SetCurrentItem(nullptr);
+ } else if (newCurrentIndex > -1) {
+ // Make sure the index is still valid
+ int32_t treeRows;
+ listBox->GetRowCount(&treeRows);
+ if (treeRows > 0) {
+ newCurrentIndex = std::min((treeRows - 1), newCurrentIndex);
+ nsCOMPtr<nsIDOMElement> newCurrentItem;
+ listBox->GetItemAtIndex(newCurrentIndex, getter_AddRefs(newCurrentItem));
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> xulCurItem = do_QueryInterface(newCurrentItem);
+ if (xulCurItem)
+ controlElement->SetCurrentItem(xulCurItem);
+ } else {
+ controlElement->SetCurrentItem(nullptr);
+ }
+ }
+
+ nsIDocument* doc;
+ if (fireSelectionHandler && (doc = GetComposedDoc())) {
+ nsContentUtils::DispatchTrustedEvent(doc,
+ static_cast<nsIContent*>(this),
+ NS_LITERAL_STRING("select"),
+ false,
+ true);
+ }
+}
+
+void
+nsXULElement::UnregisterAccessKey(const nsAString& aOldValue)
+{
+ // If someone changes the accesskey, unregister the old one
+ //
+ nsIDocument* doc = GetComposedDoc();
+ if (doc && !aOldValue.IsEmpty()) {
+ nsIPresShell *shell = doc->GetShell();
+
+ if (shell) {
+ nsIContent *content = this;
+
+ // find out what type of content node this is
+ if (mNodeInfo->Equals(nsGkAtoms::label)) {
+ // For anonymous labels the unregistering must
+ // occur on the binding parent control.
+ // XXXldb: And what if the binding parent is null?
+ content = GetBindingParent();
+ }
+
+ if (content) {
+ shell->GetPresContext()->EventStateManager()->
+ UnregisterAccessKey(content, aOldValue.First());
+ }
+ }
+ }
+}
+
+nsresult
+nsXULElement::BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue, bool aNotify)
+{
+ if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::accesskey &&
+ IsInUncomposedDoc()) {
+ nsAutoString oldValue;
+ if (GetAttr(aNamespaceID, aName, oldValue)) {
+ UnregisterAccessKey(oldValue);
+ }
+ } else if (aNamespaceID == kNameSpaceID_None &&
+ (aName == nsGkAtoms::command || aName == nsGkAtoms::observes) &&
+ IsInUncomposedDoc()) {
+// XXX sXBL/XBL2 issue! Owner or current document?
+ nsAutoString oldValue;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::observes, oldValue);
+ if (oldValue.IsEmpty()) {
+ GetAttr(kNameSpaceID_None, nsGkAtoms::command, oldValue);
+ }
+
+ if (!oldValue.IsEmpty()) {
+ RemoveBroadcaster(oldValue);
+ }
+ } else if (aNamespaceID == kNameSpaceID_None &&
+ aValue &&
+ mNodeInfo->Equals(nsGkAtoms::window) &&
+ aName == nsGkAtoms::chromemargin) {
+ nsAttrValue attrValue;
+ // Make sure the margin format is valid first
+ if (!attrValue.ParseIntMarginValue(aValue->String())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ } else if (aNamespaceID == kNameSpaceID_None &&
+ aName == nsGkAtoms::usercontextid) {
+ nsAutoString oldValue;
+ bool hasAttribute = GetAttr(kNameSpaceID_None, nsGkAtoms::usercontextid, oldValue);
+ if (hasAttribute && (!aValue || !aValue->String().Equals(oldValue))) {
+ MOZ_ASSERT(false, "Changing usercontextid is not allowed.");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ return nsStyledElement::BeforeSetAttr(aNamespaceID, aName,
+ aValue, aNotify);
+}
+
+nsresult
+nsXULElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aValue) {
+ // Add popup and event listeners. We can't call AddListenerFor since
+ // the attribute isn't set yet.
+ MaybeAddPopupListener(aName);
+ if (nsContentUtils::IsEventAttributeName(aName, EventNameType_XUL)) {
+ if (aValue->Type() == nsAttrValue::eString) {
+ SetEventHandler(aName, aValue->GetStringValue(), true);
+ } else {
+ nsAutoString body;
+ aValue->ToString(body);
+ SetEventHandler(aName, body, true);
+ }
+ }
+
+ nsIDocument* document = GetUncomposedDoc();
+
+ // Hide chrome if needed
+ if (mNodeInfo->Equals(nsGkAtoms::window)) {
+ if (aName == nsGkAtoms::hidechrome) {
+ HideWindowChrome(
+ aValue->Equals(NS_LITERAL_STRING("true"), eCaseMatters));
+ } else if (aName == nsGkAtoms::chromemargin) {
+ SetChromeMargins(aValue);
+ } else if (aName == nsGkAtoms::windowtype &&
+ document && document->GetRootElement() == this) {
+ MaybeUpdatePrivateLifetime();
+ }
+ }
+ // title, (in)activetitlebarcolor and drawintitlebar are settable on
+ // any root node (windows, dialogs, etc)
+ if (document && document->GetRootElement() == this) {
+ if (aName == nsGkAtoms::title) {
+ document->NotifyPossibleTitleChange(false);
+ } else if ((aName == nsGkAtoms::activetitlebarcolor ||
+ aName == nsGkAtoms::inactivetitlebarcolor)) {
+ nscolor color = NS_RGBA(0, 0, 0, 0);
+ if (aValue->Type() == nsAttrValue::eColor) {
+ aValue->GetColorValue(color);
+ } else {
+ nsAutoString tmp;
+ nsAttrValue attrValue;
+ aValue->ToString(tmp);
+ attrValue.ParseColor(tmp);
+ attrValue.GetColorValue(color);
+ }
+ SetTitlebarColor(color, aName == nsGkAtoms::activetitlebarcolor);
+ } else if (aName == nsGkAtoms::drawintitlebar) {
+ SetDrawsInTitlebar(
+ aValue->Equals(NS_LITERAL_STRING("true"), eCaseMatters));
+ } else if (aName == nsGkAtoms::drawtitle) {
+ SetDrawsTitle(
+ aValue->Equals(NS_LITERAL_STRING("true"), eCaseMatters));
+ } else if (aName == nsGkAtoms::localedir) {
+ // if the localedir changed on the root element, reset the document direction
+ nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(document);
+ if (xuldoc) {
+ xuldoc->ResetDocumentDirection();
+ }
+ } else if (aName == nsGkAtoms::lwtheme ||
+ aName == nsGkAtoms::lwthemetextcolor) {
+ // if the lwtheme changed, make sure to reset the document lwtheme cache
+ nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(document);
+ if (xuldoc) {
+ xuldoc->ResetDocumentLWTheme();
+ UpdateBrightTitlebarForeground(document);
+ }
+ } else if (aName == nsGkAtoms::brighttitlebarforeground) {
+ UpdateBrightTitlebarForeground(document);
+ }
+ }
+
+ if (aName == nsGkAtoms::src && document) {
+ LoadSrc();
+ }
+ } else {
+ if (mNodeInfo->Equals(nsGkAtoms::window)) {
+ if (aName == nsGkAtoms::hidechrome) {
+ HideWindowChrome(false);
+ } else if (aName == nsGkAtoms::chromemargin) {
+ ResetChromeMargins();
+ }
+ }
+
+ nsIDocument* doc = GetUncomposedDoc();
+ if (doc && doc->GetRootElement() == this) {
+ if ((aName == nsGkAtoms::activetitlebarcolor ||
+ aName == nsGkAtoms::inactivetitlebarcolor)) {
+ // Use 0, 0, 0, 0 as the "none" color.
+ SetTitlebarColor(NS_RGBA(0, 0, 0, 0), aName == nsGkAtoms::activetitlebarcolor);
+ } else if (aName == nsGkAtoms::localedir) {
+ // if the localedir changed on the root element, reset the document direction
+ nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(doc);
+ if (xuldoc) {
+ xuldoc->ResetDocumentDirection();
+ }
+ } else if ((aName == nsGkAtoms::lwtheme ||
+ aName == nsGkAtoms::lwthemetextcolor)) {
+ // if the lwtheme changed, make sure to restyle appropriately
+ nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(doc);
+ if (xuldoc) {
+ xuldoc->ResetDocumentLWTheme();
+ UpdateBrightTitlebarForeground(doc);
+ }
+ } else if (aName == nsGkAtoms::brighttitlebarforeground) {
+ UpdateBrightTitlebarForeground(doc);
+ } else if (aName == nsGkAtoms::drawintitlebar) {
+ SetDrawsInTitlebar(false);
+ } else if (aName == nsGkAtoms::drawtitle) {
+ SetDrawsTitle(false);
+ }
+ }
+ }
+
+ // XXX need to check if they're changing an event handler: if
+ // so, then we need to unhook the old one. Or something.
+ }
+
+ return nsStyledElement::AfterSetAttr(aNamespaceID, aName,
+ aValue, aNotify);
+}
+
+bool
+nsXULElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ // Parse into a nsAttrValue
+ if (!nsStyledElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult)) {
+ // Fall back to parsing as atom for short values
+ aResult.ParseStringOrAtom(aValue);
+ }
+
+ return true;
+}
+
+void
+nsXULElement::RemoveBroadcaster(const nsAString & broadcasterId)
+{
+ nsCOMPtr<nsIDOMXULDocument> xuldoc = do_QueryInterface(OwnerDoc());
+ if (xuldoc) {
+ nsCOMPtr<nsIDOMElement> broadcaster;
+ nsCOMPtr<nsIDOMDocument> domDoc (do_QueryInterface(xuldoc));
+ domDoc->GetElementById(broadcasterId, getter_AddRefs(broadcaster));
+ if (broadcaster) {
+ xuldoc->RemoveBroadcastListenerFor(broadcaster, this,
+ NS_LITERAL_STRING("*"));
+ }
+ }
+}
+
+void
+nsXULElement::DestroyContent()
+{
+ nsXULSlots* slots = static_cast<nsXULSlots*>(GetExistingDOMSlots());
+ if (slots) {
+ NS_IF_RELEASE(slots->mControllers);
+ RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
+ if (frameLoader) {
+ frameLoader->Destroy();
+ }
+ slots->mFrameLoaderOrOpener = nullptr;
+ }
+
+ nsStyledElement::DestroyContent();
+}
+
+#ifdef DEBUG
+void
+nsXULElement::List(FILE* out, int32_t aIndent) const
+{
+ nsCString prefix("XUL");
+ if (HasSlots()) {
+ prefix.Append('*');
+ }
+ prefix.Append(' ');
+
+ nsStyledElement::List(out, aIndent, prefix);
+}
+#endif
+
+nsresult
+nsXULElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ aVisitor.mForceContentDispatch = true; //FIXME! Bug 329119
+ if (IsRootOfNativeAnonymousSubtree() &&
+ (IsAnyOfXULElements(nsGkAtoms::scrollbar, nsGkAtoms::scrollcorner)) &&
+ (aVisitor.mEvent->mMessage == eMouseClick ||
+ aVisitor.mEvent->mMessage == eMouseDoubleClick ||
+ aVisitor.mEvent->mMessage == eXULCommand ||
+ aVisitor.mEvent->mMessage == eContextMenu ||
+ aVisitor.mEvent->mMessage == eDragStart)) {
+ // Don't propagate these events from native anonymous scrollbar.
+ aVisitor.mCanHandle = true;
+ aVisitor.mParentTarget = nullptr;
+ return NS_OK;
+ }
+ if (aVisitor.mEvent->mMessage == eXULCommand &&
+ aVisitor.mEvent->mClass == eInputEventClass &&
+ aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this) &&
+ !IsXULElement(nsGkAtoms::command)) {
+ // Check that we really have an xul command event. That will be handled
+ // in a special way.
+ nsCOMPtr<nsIDOMXULCommandEvent> xulEvent =
+ do_QueryInterface(aVisitor.mDOMEvent);
+ // See if we have a command elt. If so, we execute on the command
+ // instead of on our content element.
+ nsAutoString command;
+ if (xulEvent && GetAttr(kNameSpaceID_None, nsGkAtoms::command, command) &&
+ !command.IsEmpty()) {
+ // Stop building the event target chain for the original event.
+ // We don't want it to propagate to any DOM nodes.
+ aVisitor.mCanHandle = false;
+ aVisitor.mAutomaticChromeDispatch = false;
+
+ // XXX sXBL/XBL2 issue! Owner or current document?
+ nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(GetUncomposedDoc()));
+ NS_ENSURE_STATE(domDoc);
+ nsCOMPtr<nsIDOMElement> commandElt;
+ domDoc->GetElementById(command, getter_AddRefs(commandElt));
+ nsCOMPtr<nsIContent> commandContent(do_QueryInterface(commandElt));
+ if (commandContent) {
+ // Create a new command event to dispatch to the element
+ // pointed to by the command attribute. The new event's
+ // sourceEvent will be the original command event that we're
+ // handling.
+ nsCOMPtr<nsIDOMEvent> domEvent = aVisitor.mDOMEvent;
+ while (domEvent) {
+ Event* event = domEvent->InternalDOMEvent();
+ NS_ENSURE_STATE(!SameCOMIdentity(event->GetOriginalTarget(),
+ commandContent));
+ nsCOMPtr<nsIDOMXULCommandEvent> commandEvent =
+ do_QueryInterface(domEvent);
+ if (commandEvent) {
+ commandEvent->GetSourceEvent(getter_AddRefs(domEvent));
+ } else {
+ domEvent = nullptr;
+ }
+ }
+
+ WidgetInputEvent* orig = aVisitor.mEvent->AsInputEvent();
+ nsContentUtils::DispatchXULCommand(
+ commandContent,
+ aVisitor.mEvent->IsTrusted(),
+ aVisitor.mDOMEvent,
+ nullptr,
+ orig->IsControl(),
+ orig->IsAlt(),
+ orig->IsShift(),
+ orig->IsMeta());
+ } else {
+ NS_WARNING("A XUL element is attached to a command that doesn't exist!\n");
+ }
+ return NS_OK;
+ }
+ }
+
+ return nsStyledElement::PreHandleEvent(aVisitor);
+}
+
+// XXX This _should_ be an implementation method, _not_ publicly exposed :-(
+NS_IMETHODIMP
+nsXULElement::GetResource(nsIRDFResource** aResource)
+{
+ ErrorResult rv;
+ *aResource = GetResource(rv).take();
+ return rv.StealNSResult();
+}
+
+already_AddRefed<nsIRDFResource>
+nsXULElement::GetResource(ErrorResult& rv)
+{
+ nsAutoString id;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::ref, id);
+ if (id.IsEmpty()) {
+ GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
+ }
+
+ if (id.IsEmpty()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIRDFResource> resource;
+ rv = nsXULContentUtils::RDFService()->
+ GetUnicodeResource(id, getter_AddRefs(resource));
+ return resource.forget();
+}
+
+NS_IMETHODIMP
+nsXULElement::GetDatabase(nsIRDFCompositeDataSource** aDatabase)
+{
+ *aDatabase = GetDatabase().take();
+ return NS_OK;
+}
+
+already_AddRefed<nsIRDFCompositeDataSource>
+nsXULElement::GetDatabase()
+{
+ nsCOMPtr<nsIXULTemplateBuilder> builder = GetBuilder();
+ if (!builder) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIRDFCompositeDataSource> database;
+ builder->GetDatabase(getter_AddRefs(database));
+ return database.forget();
+}
+
+
+NS_IMETHODIMP
+nsXULElement::GetBuilder(nsIXULTemplateBuilder** aBuilder)
+{
+ *aBuilder = GetBuilder().take();
+ return NS_OK;
+}
+
+already_AddRefed<nsIXULTemplateBuilder>
+nsXULElement::GetBuilder()
+{
+ // XXX sXBL/XBL2 issue! Owner or current document?
+ nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(GetUncomposedDoc());
+ if (!xuldoc) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIXULTemplateBuilder> builder;
+ xuldoc->GetTemplateBuilderFor(this, getter_AddRefs(builder));
+ return builder.forget();
+}
+
+//----------------------------------------------------------------------
+// Implementation methods
+
+NS_IMETHODIMP
+nsXULElement::WalkContentStyleRules(nsRuleWalker* aRuleWalker)
+{
+ return NS_OK;
+}
+
+nsChangeHint
+nsXULElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const
+{
+ nsChangeHint retval(nsChangeHint(0));
+
+ if (aAttribute == nsGkAtoms::value &&
+ (aModType == nsIDOMMutationEvent::REMOVAL ||
+ aModType == nsIDOMMutationEvent::ADDITION)) {
+ if (IsAnyOfXULElements(nsGkAtoms::label, nsGkAtoms::description))
+ // Label and description dynamically morph between a normal
+ // block and a cropping single-line XUL text frame. If the
+ // value attribute is being added or removed, then we need to
+ // return a hint of frame change. (See bugzilla bug 95475 for
+ // details.)
+ retval = nsChangeHint_ReconstructFrame;
+ } else {
+ // if left or top changes we reflow. This will happen in xul
+ // containers that manage positioned children such as a stack.
+ if (nsGkAtoms::left == aAttribute || nsGkAtoms::top == aAttribute ||
+ nsGkAtoms::right == aAttribute || nsGkAtoms::bottom == aAttribute ||
+ nsGkAtoms::start == aAttribute || nsGkAtoms::end == aAttribute)
+ retval = NS_STYLE_HINT_REFLOW;
+ }
+
+ return retval;
+}
+
+NS_IMETHODIMP_(bool)
+nsXULElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ return false;
+}
+
+// Controllers Methods
+NS_IMETHODIMP
+nsXULElement::GetControllers(nsIControllers** aResult)
+{
+ ErrorResult rv;
+ NS_IF_ADDREF(*aResult = GetControllers(rv));
+ return rv.StealNSResult();
+}
+
+nsIControllers*
+nsXULElement::GetControllers(ErrorResult& rv)
+{
+ if (! Controllers()) {
+ nsDOMSlots* slots = DOMSlots();
+
+ rv = NS_NewXULControllers(nullptr, NS_GET_IID(nsIControllers),
+ reinterpret_cast<void**>(&slots->mControllers));
+
+ NS_ASSERTION(!rv.Failed(), "unable to create a controllers");
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ }
+
+ return Controllers();
+}
+
+NS_IMETHODIMP
+nsXULElement::GetBoxObject(nsIBoxObject** aResult)
+{
+ ErrorResult rv;
+ *aResult = GetBoxObject(rv).take();
+ return rv.StealNSResult();
+}
+
+already_AddRefed<BoxObject>
+nsXULElement::GetBoxObject(ErrorResult& rv)
+{
+ // XXX sXBL/XBL2 issue! Owner or current document?
+ return OwnerDoc()->GetBoxObjectFor(this, rv);
+}
+
+// Methods for setting/getting attributes from nsIDOMXULElement
+#define NS_IMPL_XUL_STRING_ATTR(_method, _atom) \
+ NS_IMETHODIMP \
+ nsXULElement::Get##_method(nsAString& aReturn) \
+ { \
+ GetAttr(kNameSpaceID_None, nsGkAtoms::_atom, aReturn); \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP \
+ nsXULElement::Set##_method(const nsAString& aValue) \
+ { \
+ return SetAttr(kNameSpaceID_None, nsGkAtoms::_atom, aValue, \
+ true); \
+ }
+
+#define NS_IMPL_XUL_BOOL_ATTR(_method, _atom) \
+ NS_IMETHODIMP \
+ nsXULElement::Get##_method(bool* aResult) \
+ { \
+ *aResult = _method(); \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP \
+ nsXULElement::Set##_method(bool aValue) \
+ { \
+ SetXULBoolAttr(nsGkAtoms::_atom, aValue); \
+ return NS_OK; \
+ }
+
+
+NS_IMPL_XUL_STRING_ATTR(Align, align)
+NS_IMPL_XUL_STRING_ATTR(Dir, dir)
+NS_IMPL_XUL_STRING_ATTR(Flex, flex)
+NS_IMPL_XUL_STRING_ATTR(FlexGroup, flexgroup)
+NS_IMPL_XUL_STRING_ATTR(Ordinal, ordinal)
+NS_IMPL_XUL_STRING_ATTR(Orient, orient)
+NS_IMPL_XUL_STRING_ATTR(Pack, pack)
+NS_IMPL_XUL_BOOL_ATTR(Hidden, hidden)
+NS_IMPL_XUL_BOOL_ATTR(Collapsed, collapsed)
+NS_IMPL_XUL_BOOL_ATTR(AllowEvents, allowevents)
+NS_IMPL_XUL_STRING_ATTR(Observes, observes)
+NS_IMPL_XUL_STRING_ATTR(Menu, menu)
+NS_IMPL_XUL_STRING_ATTR(ContextMenu, contextmenu)
+NS_IMPL_XUL_STRING_ATTR(Tooltip, tooltip)
+NS_IMPL_XUL_STRING_ATTR(Width, width)
+NS_IMPL_XUL_STRING_ATTR(Height, height)
+NS_IMPL_XUL_STRING_ATTR(MinWidth, minwidth)
+NS_IMPL_XUL_STRING_ATTR(MinHeight, minheight)
+NS_IMPL_XUL_STRING_ATTR(MaxWidth, maxwidth)
+NS_IMPL_XUL_STRING_ATTR(MaxHeight, maxheight)
+NS_IMPL_XUL_STRING_ATTR(Persist, persist)
+NS_IMPL_XUL_STRING_ATTR(Left, left)
+NS_IMPL_XUL_STRING_ATTR(Top, top)
+NS_IMPL_XUL_STRING_ATTR(Datasources, datasources)
+NS_IMPL_XUL_STRING_ATTR(Ref, ref)
+NS_IMPL_XUL_STRING_ATTR(TooltipText, tooltiptext)
+NS_IMPL_XUL_STRING_ATTR(StatusText, statustext)
+
+nsresult
+nsXULElement::LoadSrc()
+{
+ // Allow frame loader only on objects for which a container box object
+ // can be obtained.
+ if (!IsAnyOfXULElements(nsGkAtoms::browser, nsGkAtoms::editor,
+ nsGkAtoms::iframe)) {
+ return NS_OK;
+ }
+ if (!IsInUncomposedDoc() ||
+ !OwnerDoc()->GetRootElement() ||
+ OwnerDoc()->GetRootElement()->
+ NodeInfo()->Equals(nsGkAtoms::overlay, kNameSpaceID_XUL)) {
+ return NS_OK;
+ }
+ RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
+ if (!frameLoader) {
+ // Check if we have an opener we need to be setting
+ nsXULSlots* slots = static_cast<nsXULSlots*>(Slots());
+ nsCOMPtr<nsPIDOMWindowOuter> opener = do_QueryInterface(slots->mFrameLoaderOrOpener);
+ if (!opener) {
+ // If we are a content-primary xul-browser, we want to take the opener property!
+ nsCOMPtr<nsIDOMChromeWindow> chromeWindow = do_QueryInterface(OwnerDoc()->GetWindow());
+ if (AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ NS_LITERAL_STRING("content-primary"), eIgnoreCase) &&
+ chromeWindow) {
+ nsCOMPtr<mozIDOMWindowProxy> wp;
+ chromeWindow->TakeOpenerForInitialContentBrowser(getter_AddRefs(wp));
+ opener = nsPIDOMWindowOuter::From(wp);
+ }
+ }
+
+ // false as the last parameter so that xul:iframe/browser/editor
+ // session history handling works like dynamic html:iframes.
+ // Usually xul elements are used in chrome, which doesn't have
+ // session history at all.
+ frameLoader = nsFrameLoader::Create(this, opener, false);
+ slots->mFrameLoaderOrOpener = static_cast<nsIFrameLoader*>(frameLoader);
+ NS_ENSURE_TRUE(frameLoader, NS_OK);
+
+ (new AsyncEventDispatcher(this,
+ NS_LITERAL_STRING("XULFrameLoaderCreated"),
+ /* aBubbles */ true))->RunDOMEventWhenSafe();
+
+ if (AttrValueIs(kNameSpaceID_None, nsGkAtoms::prerendered,
+ NS_LITERAL_STRING("true"), eIgnoreCase)) {
+ nsresult rv = frameLoader->SetIsPrerendered();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+
+ return frameLoader->LoadFrame();
+}
+
+nsresult
+nsXULElement::GetFrameLoaderXPCOM(nsIFrameLoader **aFrameLoader)
+{
+ *aFrameLoader = GetFrameLoader().take();
+ return NS_OK;
+}
+
+already_AddRefed<nsFrameLoader>
+nsXULElement::GetFrameLoader()
+{
+ nsXULSlots* slots = static_cast<nsXULSlots*>(GetExistingSlots());
+ if (!slots)
+ return nullptr;
+
+ nsCOMPtr<nsIFrameLoader> loader = do_QueryInterface(slots->mFrameLoaderOrOpener);
+ return already_AddRefed<nsFrameLoader>(static_cast<nsFrameLoader*>(loader.forget().take()));
+}
+
+nsresult
+nsXULElement::GetParentApplication(mozIApplication** aApplication)
+{
+ if (!aApplication) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aApplication = nullptr;
+ return NS_OK;
+}
+
+void
+nsXULElement::PresetOpenerWindow(mozIDOMWindowProxy* aWindow, ErrorResult& aRv)
+{
+ nsXULSlots* slots = static_cast<nsXULSlots*>(Slots());
+ MOZ_ASSERT(!slots->mFrameLoaderOrOpener, "A frameLoader or opener is present when calling PresetOpenerWindow");
+
+ slots->mFrameLoaderOrOpener = aWindow;
+}
+
+nsresult
+nsXULElement::SetIsPrerendered()
+{
+ return SetAttr(kNameSpaceID_None, nsGkAtoms::prerendered, nullptr,
+ NS_LITERAL_STRING("true"), true);
+}
+
+void
+nsXULElement::InternalSetFrameLoader(nsIFrameLoader* aNewFrameLoader)
+{
+ nsXULSlots* slots = static_cast<nsXULSlots*>(GetExistingDOMSlots());
+ MOZ_ASSERT(slots);
+
+ slots->mFrameLoaderOrOpener = aNewFrameLoader;
+}
+
+void
+nsXULElement::SwapFrameLoaders(HTMLIFrameElement& aOtherLoaderOwner,
+ ErrorResult& rv)
+{
+ if (!GetExistingDOMSlots()) {
+ rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return;
+ }
+
+ nsCOMPtr<nsIFrameLoaderOwner> flo = do_QueryInterface(static_cast<nsIDOMXULElement*>(this));
+ aOtherLoaderOwner.SwapFrameLoaders(flo, rv);
+}
+
+void
+nsXULElement::SwapFrameLoaders(nsXULElement& aOtherLoaderOwner,
+ ErrorResult& rv)
+{
+ if (&aOtherLoaderOwner == this) {
+ // nothing to do
+ return;
+ }
+
+ if (!GetExistingDOMSlots()) {
+ rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return;
+ }
+
+ nsCOMPtr<nsIFrameLoaderOwner> flo = do_QueryInterface(static_cast<nsIDOMXULElement*>(this));
+ aOtherLoaderOwner.SwapFrameLoaders(flo, rv);
+}
+
+void
+nsXULElement::SwapFrameLoaders(nsIFrameLoaderOwner* aOtherLoaderOwner,
+ mozilla::ErrorResult& rv)
+{
+ if (!GetExistingDOMSlots()) {
+ rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return;
+ }
+
+ RefPtr<nsFrameLoader> loader = GetFrameLoader();
+ RefPtr<nsFrameLoader> otherLoader = aOtherLoaderOwner->GetFrameLoader();
+ if (!loader || !otherLoader) {
+ rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return;
+ }
+
+ nsCOMPtr<nsIFrameLoaderOwner> flo = do_QueryInterface(static_cast<nsIDOMXULElement*>(this));
+ rv = loader->SwapWithOtherLoader(otherLoader, flo, aOtherLoaderOwner);
+}
+
+NS_IMETHODIMP
+nsXULElement::GetParentTree(nsIDOMXULMultiSelectControlElement** aTreeElement)
+{
+ for (nsIContent* current = GetParent(); current;
+ current = current->GetParent()) {
+ if (current->NodeInfo()->Equals(nsGkAtoms::listbox,
+ kNameSpaceID_XUL)) {
+ CallQueryInterface(current, aTreeElement);
+ // XXX returning NS_OK because that's what the code used to do;
+ // is that the right thing, though?
+
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULElement::Focus()
+{
+ ErrorResult rv;
+ Focus(rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+nsXULElement::Blur()
+{
+ ErrorResult rv;
+ Blur(rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+nsXULElement::Click()
+{
+ return ClickWithInputSource(nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN, /* aIsTrusted = */ true);
+}
+
+void
+nsXULElement::Click(ErrorResult& rv)
+{
+ rv = ClickWithInputSource(nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN, nsContentUtils::IsCallerChrome());
+}
+
+nsresult
+nsXULElement::ClickWithInputSource(uint16_t aInputSource, bool aIsTrustedEvent)
+{
+ if (BoolAttrIsTrue(nsGkAtoms::disabled))
+ return NS_OK;
+
+ nsCOMPtr<nsIDocument> doc = GetComposedDoc(); // Strong just in case
+ if (doc) {
+ nsCOMPtr<nsIPresShell> shell = doc->GetShell();
+ if (shell) {
+ // strong ref to PresContext so events don't destroy it
+ RefPtr<nsPresContext> context = shell->GetPresContext();
+
+ WidgetMouseEvent eventDown(aIsTrustedEvent, eMouseDown,
+ nullptr, WidgetMouseEvent::eReal);
+ WidgetMouseEvent eventUp(aIsTrustedEvent, eMouseUp,
+ nullptr, WidgetMouseEvent::eReal);
+ WidgetMouseEvent eventClick(aIsTrustedEvent, eMouseClick, nullptr,
+ WidgetMouseEvent::eReal);
+ eventDown.inputSource = eventUp.inputSource = eventClick.inputSource
+ = aInputSource;
+
+ // send mouse down
+ nsEventStatus status = nsEventStatus_eIgnore;
+ EventDispatcher::Dispatch(static_cast<nsIContent*>(this),
+ context, &eventDown, nullptr, &status);
+
+ // send mouse up
+ status = nsEventStatus_eIgnore; // reset status
+ EventDispatcher::Dispatch(static_cast<nsIContent*>(this),
+ context, &eventUp, nullptr, &status);
+
+ // send mouse click
+ status = nsEventStatus_eIgnore; // reset status
+ EventDispatcher::Dispatch(static_cast<nsIContent*>(this),
+ context, &eventClick, nullptr, &status);
+
+ // If the click has been prevented, lets skip the command call
+ // this is how a physical click works
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ return NS_OK;
+ }
+ }
+ }
+
+ // oncommand is fired when an element is clicked...
+ return DoCommand();
+}
+
+NS_IMETHODIMP
+nsXULElement::DoCommand()
+{
+ nsCOMPtr<nsIDocument> doc = GetComposedDoc(); // strong just in case
+ if (doc) {
+ nsContentUtils::DispatchXULCommand(this, true);
+ }
+
+ return NS_OK;
+}
+
+nsIContent *
+nsXULElement::GetBindingParent() const
+{
+ return mBindingParent;
+}
+
+bool
+nsXULElement::IsNodeOfType(uint32_t aFlags) const
+{
+ return !(aFlags & ~eCONTENT);
+}
+
+nsresult
+nsXULElement::AddPopupListener(nsIAtom* aName)
+{
+ // Add a popup listener to the element
+ bool isContext = (aName == nsGkAtoms::context ||
+ aName == nsGkAtoms::contextmenu);
+ uint32_t listenerFlag = isContext ?
+ XUL_ELEMENT_HAS_CONTENTMENU_LISTENER :
+ XUL_ELEMENT_HAS_POPUP_LISTENER;
+
+ if (HasFlag(listenerFlag)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMEventListener> listener =
+ new nsXULPopupListener(this, isContext);
+
+ // Add the popup as a listener on this element.
+ EventListenerManager* manager = GetOrCreateListenerManager();
+ SetFlags(listenerFlag);
+
+ if (isContext) {
+ manager->AddEventListenerByType(listener,
+ NS_LITERAL_STRING("contextmenu"),
+ TrustedEventsAtSystemGroupBubble());
+ } else {
+ manager->AddEventListenerByType(listener,
+ NS_LITERAL_STRING("mousedown"),
+ TrustedEventsAtSystemGroupBubble());
+ }
+ return NS_OK;
+}
+
+EventStates
+nsXULElement::IntrinsicState() const
+{
+ EventStates state = nsStyledElement::IntrinsicState();
+
+ if (IsReadWriteTextElement()) {
+ state |= NS_EVENT_STATE_MOZ_READWRITE;
+ state &= ~NS_EVENT_STATE_MOZ_READONLY;
+ }
+
+ return state;
+}
+
+//----------------------------------------------------------------------
+
+nsresult
+nsXULElement::MakeHeavyweight(nsXULPrototypeElement* aPrototype)
+{
+ if (!aPrototype) {
+ return NS_OK;
+ }
+
+ uint32_t i;
+ nsresult rv;
+ for (i = 0; i < aPrototype->mNumAttributes; ++i) {
+ nsXULPrototypeAttribute* protoattr = &aPrototype->mAttributes[i];
+ nsAttrValue attrValue;
+
+ // Style rules need to be cloned.
+ if (protoattr->mValue.Type() == nsAttrValue::eCSSDeclaration) {
+ DeclarationBlock* decl = protoattr->mValue.GetCSSDeclarationValue();
+ RefPtr<css::Declaration>
+ declClone = new css::Declaration(*decl->AsGecko());
+
+ nsString stringValue;
+ protoattr->mValue.ToString(stringValue);
+
+ attrValue.SetTo(declClone.forget(), &stringValue);
+ } else {
+ attrValue.SetTo(protoattr->mValue);
+ }
+
+ // XXX we might wanna have a SetAndTakeAttr that takes an nsAttrName
+ if (protoattr->mName.IsAtom()) {
+ rv = mAttrsAndChildren.SetAndSwapAttr(protoattr->mName.Atom(), attrValue);
+ } else {
+ rv = mAttrsAndChildren.SetAndSwapAttr(protoattr->mName.NodeInfo(),
+ attrValue);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsXULElement::HideWindowChrome(bool aShouldHide)
+{
+ nsIDocument* doc = GetUncomposedDoc();
+ if (!doc || doc->GetRootElement() != this)
+ return NS_ERROR_UNEXPECTED;
+
+ // only top level chrome documents can hide the window chrome
+ if (!doc->IsRootDisplayDocument())
+ return NS_OK;
+
+ nsIPresShell *shell = doc->GetShell();
+
+ if (shell) {
+ nsIFrame* frame = GetPrimaryFrame();
+
+ nsPresContext *presContext = shell->GetPresContext();
+
+ if (frame && presContext && presContext->IsChrome()) {
+ nsView* view = frame->GetClosestView();
+
+ if (view) {
+ nsIWidget* w = view->GetWidget();
+ NS_ENSURE_STATE(w);
+ w->HideWindowChrome(aShouldHide);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsIWidget*
+nsXULElement::GetWindowWidget()
+{
+ nsIDocument* doc = GetComposedDoc();
+
+ // only top level chrome documents can set the titlebar color
+ if (doc && doc->IsRootDisplayDocument()) {
+ nsCOMPtr<nsISupports> container = doc->GetContainer();
+ nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(container);
+ if (baseWindow) {
+ nsCOMPtr<nsIWidget> mainWidget;
+ baseWindow->GetMainWidget(getter_AddRefs(mainWidget));
+ return mainWidget;
+ }
+ }
+ return nullptr;
+}
+
+void
+nsXULElement::SetTitlebarColor(nscolor aColor, bool aActive)
+{
+ nsIWidget* mainWidget = GetWindowWidget();
+ if (mainWidget) {
+ mainWidget->SetWindowTitlebarColor(aColor, aActive);
+ }
+}
+
+class SetDrawInTitleBarEvent : public Runnable
+{
+public:
+ SetDrawInTitleBarEvent(nsIWidget* aWidget, bool aState)
+ : mWidget(aWidget)
+ , mState(aState)
+ {}
+
+ NS_IMETHOD Run() override {
+ NS_ASSERTION(mWidget, "You shouldn't call this runnable with a null widget!");
+
+ mWidget->SetDrawsInTitlebar(mState);
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIWidget> mWidget;
+ bool mState;
+};
+
+void
+nsXULElement::SetDrawsInTitlebar(bool aState)
+{
+ nsIWidget* mainWidget = GetWindowWidget();
+ if (mainWidget) {
+ nsContentUtils::AddScriptRunner(new SetDrawInTitleBarEvent(mainWidget, aState));
+ }
+}
+
+void
+nsXULElement::SetDrawsTitle(bool aState)
+{
+ nsIWidget* mainWidget = GetWindowWidget();
+ if (mainWidget) {
+ // We can do this synchronously because SetDrawsTitle doesn't have any
+ // synchronous effects apart from a harmless invalidation.
+ mainWidget->SetDrawsTitle(aState);
+ }
+}
+
+void
+nsXULElement::UpdateBrightTitlebarForeground(nsIDocument* aDoc)
+{
+ nsIWidget* mainWidget = GetWindowWidget();
+ if (mainWidget) {
+ // We can do this synchronously because SetBrightTitlebarForeground doesn't have any
+ // synchronous effects apart from a harmless invalidation.
+ mainWidget->SetUseBrightTitlebarForeground(
+ aDoc->GetDocumentLWTheme() == nsIDocument::Doc_Theme_Bright ||
+ aDoc->GetRootElement()->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::brighttitlebarforeground,
+ NS_LITERAL_STRING("true"),
+ eCaseMatters));
+ }
+}
+
+class MarginSetter : public Runnable
+{
+public:
+ explicit MarginSetter(nsIWidget* aWidget) :
+ mWidget(aWidget), mMargin(-1, -1, -1, -1)
+ {}
+ MarginSetter(nsIWidget *aWidget, const LayoutDeviceIntMargin& aMargin) :
+ mWidget(aWidget), mMargin(aMargin)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ // SetNonClientMargins can dispatch native events, hence doing
+ // it off a script runner.
+ mWidget->SetNonClientMargins(mMargin);
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIWidget> mWidget;
+ LayoutDeviceIntMargin mMargin;
+};
+
+void
+nsXULElement::SetChromeMargins(const nsAttrValue* aValue)
+{
+ if (!aValue)
+ return;
+
+ nsIWidget* mainWidget = GetWindowWidget();
+ if (!mainWidget)
+ return;
+
+ // top, right, bottom, left - see nsAttrValue
+ nsIntMargin margins;
+ bool gotMargins = false;
+
+ if (aValue->Type() == nsAttrValue::eIntMarginValue) {
+ gotMargins = aValue->GetIntMarginValue(margins);
+ } else {
+ nsAutoString tmp;
+ aValue->ToString(tmp);
+ gotMargins = nsContentUtils::ParseIntMarginValue(tmp, margins);
+ }
+ if (gotMargins) {
+ nsContentUtils::AddScriptRunner(
+ new MarginSetter(
+ mainWidget, LayoutDeviceIntMargin::FromUnknownMargin(margins)));
+ }
+}
+
+void
+nsXULElement::ResetChromeMargins()
+{
+ nsIWidget* mainWidget = GetWindowWidget();
+ if (!mainWidget)
+ return;
+ // See nsIWidget
+ nsContentUtils::AddScriptRunner(new MarginSetter(mainWidget));
+}
+
+bool
+nsXULElement::BoolAttrIsTrue(nsIAtom* aName) const
+{
+ const nsAttrValue* attr =
+ GetAttrInfo(kNameSpaceID_None, aName).mValue;
+
+ return attr && attr->Type() == nsAttrValue::eAtom &&
+ attr->GetAtomValue() == nsGkAtoms::_true;
+}
+
+void
+nsXULElement::RecompileScriptEventListeners()
+{
+ int32_t i, count = mAttrsAndChildren.AttrCount();
+ for (i = 0; i < count; ++i) {
+ const nsAttrName *name = mAttrsAndChildren.AttrNameAt(i);
+
+ // Eventlistenener-attributes are always in the null namespace
+ if (!name->IsAtom()) {
+ continue;
+ }
+
+ nsIAtom *attr = name->Atom();
+ if (!nsContentUtils::IsEventAttributeName(attr, EventNameType_XUL)) {
+ continue;
+ }
+
+ nsAutoString value;
+ GetAttr(kNameSpaceID_None, attr, value);
+ SetEventHandler(attr, value, true);
+ }
+}
+
+bool
+nsXULElement::IsEventAttributeName(nsIAtom *aName)
+{
+ return nsContentUtils::IsEventAttributeName(aName, EventNameType_XUL);
+}
+
+JSObject*
+nsXULElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return dom::XULElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULPrototypeNode)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULPrototypeNode)
+ if (tmp->mType == nsXULPrototypeNode::eType_Element) {
+ static_cast<nsXULPrototypeElement*>(tmp)->Unlink();
+ } else if (tmp->mType == nsXULPrototypeNode::eType_Script) {
+ static_cast<nsXULPrototypeScript*>(tmp)->UnlinkJSObjects();
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULPrototypeNode)
+ if (tmp->mType == nsXULPrototypeNode::eType_Element) {
+ nsXULPrototypeElement *elem =
+ static_cast<nsXULPrototypeElement*>(tmp);
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mNodeInfo");
+ cb.NoteNativeChild(elem->mNodeInfo,
+ NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo));
+ uint32_t i;
+ for (i = 0; i < elem->mNumAttributes; ++i) {
+ const nsAttrName& name = elem->mAttributes[i].mName;
+ if (!name.IsAtom()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+ "mAttributes[i].mName.NodeInfo()");
+ cb.NoteNativeChild(name.NodeInfo(),
+ NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo));
+ }
+ }
+ ImplCycleCollectionTraverse(cb, elem->mChildren, "mChildren");
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsXULPrototypeNode)
+ if (tmp->mType == nsXULPrototypeNode::eType_Script) {
+ nsXULPrototypeScript *script =
+ static_cast<nsXULPrototypeScript*>(tmp);
+ script->Trace(aCallbacks, aClosure);
+ }
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsXULPrototypeNode, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsXULPrototypeNode, Release)
+
+//----------------------------------------------------------------------
+//
+// nsXULPrototypeAttribute
+//
+
+nsXULPrototypeAttribute::~nsXULPrototypeAttribute()
+{
+ MOZ_COUNT_DTOR(nsXULPrototypeAttribute);
+}
+
+
+//----------------------------------------------------------------------
+//
+// nsXULPrototypeElement
+//
+
+nsresult
+nsXULPrototypeElement::Serialize(nsIObjectOutputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos)
+{
+ nsresult rv;
+
+ // Write basic prototype data
+ rv = aStream->Write32(mType);
+
+ // Write Node Info
+ int32_t index = aNodeInfos->IndexOf(mNodeInfo);
+ NS_ASSERTION(index >= 0, "unknown mozilla::dom::NodeInfo index");
+ nsresult tmp = aStream->Write32(index);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ // Write Attributes
+ tmp = aStream->Write32(mNumAttributes);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ nsAutoString attributeValue;
+ uint32_t i;
+ for (i = 0; i < mNumAttributes; ++i) {
+ RefPtr<mozilla::dom::NodeInfo> ni;
+ if (mAttributes[i].mName.IsAtom()) {
+ ni = mNodeInfo->NodeInfoManager()->
+ GetNodeInfo(mAttributes[i].mName.Atom(), nullptr,
+ kNameSpaceID_None,
+ nsIDOMNode::ATTRIBUTE_NODE);
+ NS_ASSERTION(ni, "the nodeinfo should already exist");
+ } else {
+ ni = mAttributes[i].mName.NodeInfo();
+ }
+
+ index = aNodeInfos->IndexOf(ni);
+ NS_ASSERTION(index >= 0, "unknown mozilla::dom::NodeInfo index");
+ tmp = aStream->Write32(index);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ mAttributes[i].mValue.ToString(attributeValue);
+ tmp = aStream->WriteWStringZ(attributeValue.get());
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+
+ // Now write children
+ tmp = aStream->Write32(uint32_t(mChildren.Length()));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ for (i = 0; i < mChildren.Length(); i++) {
+ nsXULPrototypeNode* child = mChildren[i].get();
+ switch (child->mType) {
+ case eType_Element:
+ case eType_Text:
+ case eType_PI:
+ tmp = child->Serialize(aStream, aProtoDoc, aNodeInfos);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ break;
+ case eType_Script:
+ tmp = aStream->Write32(child->mType);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ nsXULPrototypeScript* script = static_cast<nsXULPrototypeScript*>(child);
+
+ tmp = aStream->Write8(script->mOutOfLine);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ if (! script->mOutOfLine) {
+ tmp = script->Serialize(aStream, aProtoDoc, aNodeInfos);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ } else {
+ tmp = aStream->WriteCompoundObject(script->mSrcURI,
+ NS_GET_IID(nsIURI),
+ true);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ if (script->HasScriptObject()) {
+ // This may return NS_OK without muxing script->mSrcURI's
+ // data into the cache file, in the case where that
+ // muxed document is already there (written by a prior
+ // session, or by an earlier cache episode during this
+ // session).
+ tmp = script->SerializeOutOfLine(aStream, aProtoDoc);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+nsXULPrototypeElement::Deserialize(nsIObjectInputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc,
+ nsIURI* aDocumentURI,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos)
+{
+ NS_PRECONDITION(aNodeInfos, "missing nodeinfo array");
+
+ // Read Node Info
+ uint32_t number = 0;
+ nsresult rv = aStream->Read32(&number);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+ mNodeInfo = aNodeInfos->SafeElementAt(number, nullptr);
+ if (!mNodeInfo) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Read Attributes
+ rv = aStream->Read32(&number);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+ mNumAttributes = int32_t(number);
+
+ if (mNumAttributes > 0) {
+ mAttributes = new (fallible) nsXULPrototypeAttribute[mNumAttributes];
+ if (!mAttributes) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsAutoString attributeValue;
+ for (uint32_t i = 0; i < mNumAttributes; ++i) {
+ rv = aStream->Read32(&number);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+ mozilla::dom::NodeInfo* ni = aNodeInfos->SafeElementAt(number, nullptr);
+ if (!ni) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mAttributes[i].mName.SetTo(ni);
+
+ rv = aStream->ReadString(attributeValue);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+ rv = SetAttrAt(i, attributeValue, aDocumentURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+ }
+ }
+
+ rv = aStream->Read32(&number);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+ uint32_t numChildren = int32_t(number);
+
+ if (numChildren > 0) {
+ if (!mChildren.SetCapacity(numChildren, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < numChildren; i++) {
+ rv = aStream->Read32(&number);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+ Type childType = (Type)number;
+
+ RefPtr<nsXULPrototypeNode> child;
+
+ switch (childType) {
+ case eType_Element:
+ child = new nsXULPrototypeElement();
+ rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI,
+ aNodeInfos);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+ break;
+ case eType_Text:
+ child = new nsXULPrototypeText();
+ rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI,
+ aNodeInfos);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+ break;
+ case eType_PI:
+ child = new nsXULPrototypePI();
+ rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI,
+ aNodeInfos);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+ break;
+ case eType_Script: {
+ // language version/options obtained during deserialization.
+ RefPtr<nsXULPrototypeScript> script = new nsXULPrototypeScript(0, 0);
+
+ rv = aStream->ReadBoolean(&script->mOutOfLine);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+ if (!script->mOutOfLine) {
+ rv = script->Deserialize(aStream, aProtoDoc, aDocumentURI,
+ aNodeInfos);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+ } else {
+ nsCOMPtr<nsISupports> supports;
+ rv = aStream->ReadObject(true, getter_AddRefs(supports));
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+ script->mSrcURI = do_QueryInterface(supports);
+
+ rv = script->DeserializeOutOfLine(aStream, aProtoDoc);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+ }
+
+ child = script.forget();
+ break;
+ }
+ default:
+ MOZ_ASSERT(false, "Unexpected child type!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ MOZ_ASSERT(child, "Don't append null to mChildren");
+ MOZ_ASSERT(child->mType == childType);
+ mChildren.AppendElement(child);
+
+ // Oh dear. Something failed during the deserialization.
+ // We don't know what. But likely consequences of failed
+ // deserializations included calls to |AbortCaching| which
+ // shuts down the cache and closes our streams.
+ // If that happens, next time through this loop, we die a messy
+ // death. So, let's just fail now, and propagate that failure
+ // upward so that the ChromeProtocolHandler knows it can't use
+ // a cached chrome channel for this.
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+nsXULPrototypeElement::SetAttrAt(uint32_t aPos, const nsAString& aValue,
+ nsIURI* aDocumentURI)
+{
+ NS_PRECONDITION(aPos < mNumAttributes, "out-of-bounds");
+
+ // WARNING!!
+ // This code is largely duplicated in nsXULElement::SetAttr.
+ // Any changes should be made to both functions.
+
+ if (!mNodeInfo->NamespaceEquals(kNameSpaceID_XUL)) {
+ mAttributes[aPos].mValue.ParseStringOrAtom(aValue);
+
+ return NS_OK;
+ }
+
+ if (mAttributes[aPos].mName.Equals(nsGkAtoms::id) &&
+ !aValue.IsEmpty()) {
+ mHasIdAttribute = true;
+ // Store id as atom.
+ // id="" means that the element has no id. Not that it has
+ // emptystring as id.
+ mAttributes[aPos].mValue.ParseAtom(aValue);
+
+ return NS_OK;
+ } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::_class)) {
+ mHasClassAttribute = true;
+ // Compute the element's class list
+ mAttributes[aPos].mValue.ParseAtomArray(aValue);
+
+ return NS_OK;
+ } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::style)) {
+ mHasStyleAttribute = true;
+ // Parse the element's 'style' attribute
+
+ nsCSSParser parser;
+
+ // XXX Get correct Base URI (need GetBaseURI on *prototype* element)
+ // TODO: If we implement Content Security Policy for chrome documents
+ // as has been discussed, the CSP should be checked here to see if
+ // inline styles are allowed to be applied.
+ RefPtr<css::Declaration> declaration =
+ parser.ParseStyleAttribute(aValue, aDocumentURI, aDocumentURI,
+ // This is basically duplicating what
+ // nsINode::NodePrincipal() does
+ mNodeInfo->NodeInfoManager()->
+ DocumentPrincipal());
+ if (declaration) {
+ mAttributes[aPos].mValue.SetTo(declaration.forget(), &aValue);
+
+ return NS_OK;
+ }
+ // Don't abort if parsing failed, it could just be malformed css.
+ }
+
+ mAttributes[aPos].mValue.ParseStringOrAtom(aValue);
+
+ return NS_OK;
+}
+
+void
+nsXULPrototypeElement::Unlink()
+{
+ mNumAttributes = 0;
+ delete[] mAttributes;
+ mAttributes = nullptr;
+ mChildren.Clear();
+}
+
+void
+nsXULPrototypeElement::TraceAllScripts(JSTracer* aTrc)
+{
+ for (uint32_t i = 0; i < mChildren.Length(); ++i) {
+ nsXULPrototypeNode* child = mChildren[i];
+ if (child->mType == nsXULPrototypeNode::eType_Element) {
+ static_cast<nsXULPrototypeElement*>(child)->TraceAllScripts(aTrc);
+ } else if (child->mType == nsXULPrototypeNode::eType_Script) {
+ static_cast<nsXULPrototypeScript*>(child)->TraceScriptObject(aTrc);
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+//
+// nsXULPrototypeScript
+//
+
+nsXULPrototypeScript::nsXULPrototypeScript(uint32_t aLineNo, uint32_t aVersion)
+ : nsXULPrototypeNode(eType_Script),
+ mLineNo(aLineNo),
+ mSrcLoading(false),
+ mOutOfLine(true),
+ mSrcLoadWaiters(nullptr),
+ mLangVersion(aVersion),
+ mScriptObject(nullptr)
+{
+}
+
+
+nsXULPrototypeScript::~nsXULPrototypeScript()
+{
+ UnlinkJSObjects();
+}
+
+nsresult
+nsXULPrototypeScript::Serialize(nsIObjectOutputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos)
+{
+ NS_ENSURE_TRUE(aProtoDoc, NS_ERROR_UNEXPECTED);
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(xpc::CompilationScope())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr ||
+ !mScriptObject,
+ "script source still loading when serializing?!");
+ if (!mScriptObject)
+ return NS_ERROR_FAILURE;
+
+ // Write basic prototype data
+ nsresult rv;
+ rv = aStream->Write32(mLineNo);
+ if (NS_FAILED(rv)) return rv;
+ rv = aStream->Write32(mLangVersion);
+ if (NS_FAILED(rv)) return rv;
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSScript*> script(cx, mScriptObject);
+ MOZ_ASSERT(xpc::CompilationScope() == JS::CurrentGlobalOrNull(cx));
+ return nsContentUtils::XPConnect()->WriteScript(aStream, cx, script);
+}
+
+nsresult
+nsXULPrototypeScript::SerializeOutOfLine(nsIObjectOutputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc)
+{
+ nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
+
+ bool isChrome = false;
+ if (NS_FAILED(mSrcURI->SchemeIs("chrome", &isChrome)) || !isChrome)
+ // Don't cache scripts that don't come from chrome uris.
+ return rv;
+
+ nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
+ if (!cache)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ASSERTION(cache->IsEnabled(),
+ "writing to the cache file, but the XUL cache is off?");
+ bool exists;
+ cache->HasData(mSrcURI, &exists);
+
+ /* return will be NS_OK from GetAsciiSpec.
+ * that makes no sense.
+ * nor does returning NS_OK from HasMuxedDocument.
+ * XXX return something meaningful.
+ */
+ if (exists)
+ return NS_OK;
+
+ nsCOMPtr<nsIObjectOutputStream> oos;
+ rv = cache->GetOutputStream(mSrcURI, getter_AddRefs(oos));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsresult tmp = Serialize(oos, aProtoDoc, nullptr);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = cache->FinishOutputStream(mSrcURI);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ if (NS_FAILED(rv))
+ cache->AbortCaching();
+ return rv;
+}
+
+
+nsresult
+nsXULPrototypeScript::Deserialize(nsIObjectInputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc,
+ nsIURI* aDocumentURI,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos)
+{
+ nsresult rv;
+ NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr ||
+ !mScriptObject,
+ "prototype script not well-initialized when deserializing?!");
+
+ // Read basic prototype data
+ rv = aStream->Read32(&mLineNo);
+ if (NS_FAILED(rv)) return rv;
+ rv = aStream->Read32(&mLangVersion);
+ if (NS_FAILED(rv)) return rv;
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(xpc::CompilationScope())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JSScript*> newScriptObject(cx);
+ rv = nsContentUtils::XPConnect()->ReadScript(aStream, cx,
+ newScriptObject.address());
+ NS_ENSURE_SUCCESS(rv, rv);
+ Set(newScriptObject);
+ return NS_OK;
+}
+
+
+nsresult
+nsXULPrototypeScript::DeserializeOutOfLine(nsIObjectInputStream* aInput,
+ nsXULPrototypeDocument* aProtoDoc)
+{
+ // Keep track of failure via rv, so we can
+ // AbortCaching if things look bad.
+ nsresult rv = NS_OK;
+ nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
+
+ nsCOMPtr<nsIObjectInputStream> objectInput = aInput;
+ if (cache) {
+ bool useXULCache = true;
+ if (mSrcURI) {
+ // NB: we must check the XUL script cache early, to avoid
+ // multiple deserialization attempts for a given script.
+ // Note that XULDocument::LoadScript
+ // checks the XUL script cache too, in order to handle the
+ // serialization case.
+ //
+ // We need do this only for <script src='strres.js'> and the
+ // like, i.e., out-of-line scripts that are included by several
+ // different XUL documents stored in the cache file.
+ useXULCache = cache->IsEnabled();
+
+ if (useXULCache) {
+ JSScript* newScriptObject =
+ cache->GetScript(mSrcURI);
+ if (newScriptObject)
+ Set(newScriptObject);
+ }
+ }
+
+ if (!mScriptObject) {
+ if (mSrcURI) {
+ rv = cache->GetInputStream(mSrcURI, getter_AddRefs(objectInput));
+ }
+ // If !mSrcURI, we have an inline script. We shouldn't have
+ // to do anything else in that case, I think.
+
+ // We do reflect errors into rv, but our caller may want to
+ // ignore our return value, because mScriptObject will be null
+ // after any error, and that suffices to cause the script to
+ // be reloaded (from the src= URI, if any) and recompiled.
+ // We're better off slow-loading than bailing out due to a
+ // error.
+ if (NS_SUCCEEDED(rv))
+ rv = Deserialize(objectInput, aProtoDoc, nullptr, nullptr);
+
+ if (NS_SUCCEEDED(rv)) {
+ if (useXULCache && mSrcURI) {
+ bool isChrome = false;
+ mSrcURI->SchemeIs("chrome", &isChrome);
+ if (isChrome) {
+ JS::Rooted<JSScript*> script(RootingCx(), GetScriptObject());
+ cache->PutScript(mSrcURI, script);
+ }
+ }
+ cache->FinishInputStream(mSrcURI);
+ } else {
+ // If mSrcURI is not in the cache,
+ // rv will be NS_ERROR_NOT_AVAILABLE and we'll try to
+ // update the cache file to hold a serialization of
+ // this script, once it has finished loading.
+ if (rv != NS_ERROR_NOT_AVAILABLE)
+ cache->AbortCaching();
+ }
+ }
+ }
+ return rv;
+}
+
+class NotifyOffThreadScriptCompletedRunnable : public Runnable
+{
+ // An array of all outstanding script receivers. All reference counting of
+ // these objects happens on the main thread. When we return to the main
+ // thread from script compilation we make sure our receiver is still in
+ // this array (still alive) before proceeding. This array is cleared during
+ // shutdown, potentially before all outstanding script compilations have
+ // finished. We do not need to worry about pointer replay here, because
+ // a) we should not be starting script compilation after clearing this
+ // array and b) in all other cases the receiver will still be alive.
+ static StaticAutoPtr<nsTArray<nsCOMPtr<nsIOffThreadScriptReceiver>>> sReceivers;
+ static bool sSetupClearOnShutdown;
+
+ nsIOffThreadScriptReceiver* mReceiver;
+ void *mToken;
+
+public:
+ NotifyOffThreadScriptCompletedRunnable(nsIOffThreadScriptReceiver* aReceiver,
+ void *aToken)
+ : mReceiver(aReceiver), mToken(aToken)
+ {}
+
+ static void NoteReceiver(nsIOffThreadScriptReceiver* aReceiver) {
+ if (!sSetupClearOnShutdown) {
+ ClearOnShutdown(&sReceivers);
+ sSetupClearOnShutdown = true;
+ sReceivers = new nsTArray<nsCOMPtr<nsIOffThreadScriptReceiver>>();
+ }
+
+ // If we ever crash here, it's because we tried to lazy compile script
+ // too late in shutdown.
+ sReceivers->AppendElement(aReceiver);
+ }
+
+ NS_DECL_NSIRUNNABLE
+};
+
+StaticAutoPtr<nsTArray<nsCOMPtr<nsIOffThreadScriptReceiver>>> NotifyOffThreadScriptCompletedRunnable::sReceivers;
+bool NotifyOffThreadScriptCompletedRunnable::sSetupClearOnShutdown = false;
+
+NS_IMETHODIMP
+NotifyOffThreadScriptCompletedRunnable::Run()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ JS::Rooted<JSScript*> script(RootingCx());
+ {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(xpc::CompilationScope())) {
+ // Now what? I guess we just leak... this should probably never
+ // happen.
+ return NS_ERROR_UNEXPECTED;
+ }
+ JSContext* cx = jsapi.cx();
+ script = JS::FinishOffThreadScript(cx, mToken);
+ }
+
+ if (!sReceivers) {
+ // We've already shut down.
+ return NS_OK;
+ }
+
+ auto index = sReceivers->IndexOf(mReceiver);
+ MOZ_RELEASE_ASSERT(index != sReceivers->NoIndex);
+ nsCOMPtr<nsIOffThreadScriptReceiver> receiver = (*sReceivers)[index].forget();
+ sReceivers->RemoveElementAt(index);
+
+ return receiver->OnScriptCompileComplete(script, script ? NS_OK : NS_ERROR_FAILURE);
+}
+
+static void
+OffThreadScriptReceiverCallback(void *aToken, void *aCallbackData)
+{
+ // Be careful not to adjust the refcount on the receiver, as this callback
+ // may be invoked off the main thread.
+ nsIOffThreadScriptReceiver* aReceiver = static_cast<nsIOffThreadScriptReceiver*>(aCallbackData);
+ RefPtr<NotifyOffThreadScriptCompletedRunnable> notify =
+ new NotifyOffThreadScriptCompletedRunnable(aReceiver, aToken);
+ NS_DispatchToMainThread(notify);
+}
+
+nsresult
+nsXULPrototypeScript::Compile(JS::SourceBufferHolder& aSrcBuf,
+ nsIURI* aURI, uint32_t aLineNo,
+ nsIDocument* aDocument,
+ nsIOffThreadScriptReceiver *aOffThreadReceiver /* = nullptr */)
+{
+ // We'll compile the script in the compilation scope.
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(xpc::CompilationScope())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ JSContext* cx = jsapi.cx();
+
+ nsresult rv;
+ nsAutoCString urlspec;
+ nsContentUtils::GetWrapperSafeScriptFilename(aDocument, aURI, urlspec, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Ok, compile it to create a prototype script object!
+ NS_ENSURE_TRUE(JSVersion(mLangVersion) != JSVERSION_UNKNOWN, NS_OK);
+ JS::CompileOptions options(cx);
+ options.setIntroductionType("scriptElement")
+ .setFileAndLine(urlspec.get(), aLineNo)
+ .setVersion(JSVersion(mLangVersion));
+ // If the script was inline, tell the JS parser to save source for
+ // Function.prototype.toSource(). If it's out of line, we retrieve the
+ // source from the files on demand.
+ options.setSourceIsLazy(mOutOfLine);
+ JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
+ if (scope) {
+ JS::ExposeObjectToActiveJS(scope);
+ }
+
+ if (aOffThreadReceiver && JS::CanCompileOffThread(cx, options, aSrcBuf.length())) {
+ if (!JS::CompileOffThread(cx, options,
+ aSrcBuf.get(), aSrcBuf.length(),
+ OffThreadScriptReceiverCallback,
+ static_cast<void*>(aOffThreadReceiver))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ NotifyOffThreadScriptCompletedRunnable::NoteReceiver(aOffThreadReceiver);
+ } else {
+ JS::Rooted<JSScript*> script(cx);
+ if (!JS::Compile(cx, options, aSrcBuf, &script))
+ return NS_ERROR_OUT_OF_MEMORY;
+ Set(script);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsXULPrototypeScript::Compile(const char16_t* aText,
+ int32_t aTextLength,
+ nsIURI* aURI,
+ uint32_t aLineNo,
+ nsIDocument* aDocument,
+ nsIOffThreadScriptReceiver *aOffThreadReceiver /* = nullptr */)
+{
+ JS::SourceBufferHolder srcBuf(aText, aTextLength,
+ JS::SourceBufferHolder::NoOwnership);
+ return Compile(srcBuf, aURI, aLineNo, aDocument, aOffThreadReceiver);
+}
+
+void
+nsXULPrototypeScript::UnlinkJSObjects()
+{
+ if (mScriptObject) {
+ mScriptObject = nullptr;
+ mozilla::DropJSObjects(this);
+ }
+}
+
+void
+nsXULPrototypeScript::Set(JSScript* aObject)
+{
+ MOZ_ASSERT(!mScriptObject, "Leaking script object.");
+ if (!aObject) {
+ mScriptObject = nullptr;
+ return;
+ }
+
+ mScriptObject = aObject;
+ mozilla::HoldJSObjects(this);
+}
+
+//----------------------------------------------------------------------
+//
+// nsXULPrototypeText
+//
+
+nsresult
+nsXULPrototypeText::Serialize(nsIObjectOutputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos)
+{
+ nsresult rv;
+
+ // Write basic prototype data
+ rv = aStream->Write32(mType);
+
+ nsresult tmp = aStream->WriteWStringZ(mValue.get());
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ return rv;
+}
+
+nsresult
+nsXULPrototypeText::Deserialize(nsIObjectInputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc,
+ nsIURI* aDocumentURI,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos)
+{
+ nsresult rv = aStream->ReadString(mValue);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+//
+// nsXULPrototypePI
+//
+
+nsresult
+nsXULPrototypePI::Serialize(nsIObjectOutputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos)
+{
+ nsresult rv;
+
+ // Write basic prototype data
+ rv = aStream->Write32(mType);
+
+ nsresult tmp = aStream->WriteWStringZ(mTarget.get());
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = aStream->WriteWStringZ(mData.get());
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ return rv;
+}
+
+nsresult
+nsXULPrototypePI::Deserialize(nsIObjectInputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc,
+ nsIURI* aDocumentURI,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos)
+{
+ nsresult rv;
+
+ rv = aStream->ReadString(mTarget);
+ if (NS_FAILED(rv)) return rv;
+ rv = aStream->ReadString(mData);
+ if (NS_FAILED(rv)) return rv;
+
+ return rv;
+}
diff --git a/dom/xul/nsXULElement.h b/dom/xul/nsXULElement.h
new file mode 100644
index 0000000000..164afacd32
--- /dev/null
+++ b/dom/xul/nsXULElement.h
@@ -0,0 +1,707 @@
+/* -*- 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/. */
+
+/*
+
+ The base XUL element class and associates.
+
+*/
+
+#ifndef nsXULElement_h__
+#define nsXULElement_h__
+
+#include "js/TracingAPI.h"
+#include "mozilla/Attributes.h"
+#include "nsIDOMEvent.h"
+#include "nsIServiceManager.h"
+#include "nsIAtom.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsIControllers.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMXULElement.h"
+#include "nsIDOMXULMultSelectCntrlEl.h"
+#include "nsIRDFCompositeDataSource.h"
+#include "nsIRDFResource.h"
+#include "nsIURI.h"
+#include "nsIXULTemplateBuilder.h"
+#include "nsLayoutCID.h"
+#include "nsAttrAndChildArray.h"
+#include "nsGkAtoms.h"
+#include "nsStyledElement.h"
+#include "nsIFrameLoader.h"
+#include "nsFrameLoader.h"
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/ElementInlines.h"
+
+class nsIDocument;
+class nsString;
+class nsXULPrototypeDocument;
+
+class nsIObjectInputStream;
+class nsIObjectOutputStream;
+class nsIOffThreadScriptReceiver;
+class nsXULPrototypeNode;
+typedef nsTArray<RefPtr<nsXULPrototypeNode> > nsPrototypeArray;
+
+namespace mozilla {
+class EventChainPreVisitor;
+class EventListenerManager;
+namespace css {
+class StyleRule;
+} // namespace css
+namespace dom {
+class BoxObject;
+class HTMLIFrameElement;
+} // namespace dom
+} // namespace mozilla
+
+namespace JS {
+class SourceBufferHolder;
+} // namespace JS
+
+////////////////////////////////////////////////////////////////////////
+
+#ifdef XUL_PROTOTYPE_ATTRIBUTE_METERING
+#define XUL_PROTOTYPE_ATTRIBUTE_METER(counter) (nsXULPrototypeAttribute::counter++)
+#else
+#define XUL_PROTOTYPE_ATTRIBUTE_METER(counter) ((void) 0)
+#endif
+
+
+/**
+
+ A prototype attribute for an nsXULPrototypeElement.
+
+ */
+
+class nsXULPrototypeAttribute
+{
+public:
+ nsXULPrototypeAttribute()
+ : mName(nsGkAtoms::id) // XXX this is a hack, but names have to have a value
+ {
+ XUL_PROTOTYPE_ATTRIBUTE_METER(gNumAttributes);
+ MOZ_COUNT_CTOR(nsXULPrototypeAttribute);
+ }
+
+ ~nsXULPrototypeAttribute();
+
+ nsAttrName mName;
+ nsAttrValue mValue;
+
+#ifdef XUL_PROTOTYPE_ATTRIBUTE_METERING
+ static uint32_t gNumElements;
+ static uint32_t gNumAttributes;
+ static uint32_t gNumCacheTests;
+ static uint32_t gNumCacheHits;
+ static uint32_t gNumCacheSets;
+ static uint32_t gNumCacheFills;
+#endif /* !XUL_PROTOTYPE_ATTRIBUTE_METERING */
+};
+
+
+/**
+
+ A prototype content model element that holds the "primordial" values
+ that have been parsed from the original XUL document.
+
+ */
+
+class nsXULPrototypeNode
+{
+public:
+ enum Type { eType_Element, eType_Script, eType_Text, eType_PI };
+
+ Type mType;
+
+ virtual nsresult Serialize(nsIObjectOutputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) = 0;
+ virtual nsresult Deserialize(nsIObjectInputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc,
+ nsIURI* aDocumentURI,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) = 0;
+
+ /**
+ * The prototype document must call ReleaseSubtree when it is going
+ * away. This makes the parents through the tree stop owning their
+ * children, whether or not the parent's reference count is zero.
+ * Individual elements may still own individual prototypes, but
+ * those prototypes no longer remember their children to allow them
+ * to be constructed.
+ */
+ virtual void ReleaseSubtree() { }
+
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(nsXULPrototypeNode)
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsXULPrototypeNode)
+
+protected:
+ explicit nsXULPrototypeNode(Type aType)
+ : mType(aType) {}
+ virtual ~nsXULPrototypeNode() {}
+};
+
+class nsXULPrototypeElement : public nsXULPrototypeNode
+{
+public:
+ nsXULPrototypeElement()
+ : nsXULPrototypeNode(eType_Element),
+ mNumAttributes(0),
+ mHasIdAttribute(false),
+ mHasClassAttribute(false),
+ mHasStyleAttribute(false),
+ mAttributes(nullptr)
+ {
+ }
+
+ virtual ~nsXULPrototypeElement()
+ {
+ Unlink();
+ }
+
+ virtual void ReleaseSubtree() override
+ {
+ for (int32_t i = mChildren.Length() - 1; i >= 0; i--) {
+ if (mChildren[i].get())
+ mChildren[i]->ReleaseSubtree();
+ }
+ mChildren.Clear();
+ nsXULPrototypeNode::ReleaseSubtree();
+ }
+
+ virtual nsresult Serialize(nsIObjectOutputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) override;
+ virtual nsresult Deserialize(nsIObjectInputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc,
+ nsIURI* aDocumentURI,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) override;
+
+ nsresult SetAttrAt(uint32_t aPos, const nsAString& aValue, nsIURI* aDocumentURI);
+
+ void Unlink();
+
+ // Trace all scripts held by this element and its children.
+ void TraceAllScripts(JSTracer* aTrc);
+
+ nsPrototypeArray mChildren;
+
+ RefPtr<mozilla::dom::NodeInfo> mNodeInfo;
+
+ uint32_t mNumAttributes:29;
+ uint32_t mHasIdAttribute:1;
+ uint32_t mHasClassAttribute:1;
+ uint32_t mHasStyleAttribute:1;
+ nsXULPrototypeAttribute* mAttributes; // [OWNER]
+};
+
+namespace mozilla {
+namespace dom {
+class XULDocument;
+} // namespace dom
+} // namespace mozilla
+
+class nsXULPrototypeScript : public nsXULPrototypeNode
+{
+public:
+ nsXULPrototypeScript(uint32_t aLineNo, uint32_t version);
+ virtual ~nsXULPrototypeScript();
+
+ virtual nsresult Serialize(nsIObjectOutputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) override;
+ nsresult SerializeOutOfLine(nsIObjectOutputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc);
+ virtual nsresult Deserialize(nsIObjectInputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc,
+ nsIURI* aDocumentURI,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) override;
+ nsresult DeserializeOutOfLine(nsIObjectInputStream* aInput,
+ nsXULPrototypeDocument* aProtoDoc);
+
+ nsresult Compile(JS::SourceBufferHolder& aSrcBuf,
+ nsIURI* aURI, uint32_t aLineNo,
+ nsIDocument* aDocument,
+ nsIOffThreadScriptReceiver *aOffThreadReceiver = nullptr);
+
+ nsresult Compile(const char16_t* aText, int32_t aTextLength,
+ nsIURI* aURI, uint32_t aLineNo,
+ nsIDocument* aDocument,
+ nsIOffThreadScriptReceiver *aOffThreadReceiver = nullptr);
+
+ void UnlinkJSObjects();
+
+ void Set(JSScript* aObject);
+
+ bool HasScriptObject()
+ {
+ // Conversion to bool doesn't trigger mScriptObject's read barrier.
+ return mScriptObject;
+ }
+
+ JSScript* GetScriptObject()
+ {
+ return mScriptObject;
+ }
+
+ void TraceScriptObject(JSTracer* aTrc)
+ {
+ JS::TraceEdge(aTrc, &mScriptObject, "active window XUL prototype script");
+ }
+
+ void Trace(const TraceCallbacks& aCallbacks, void* aClosure)
+ {
+ if (mScriptObject) {
+ aCallbacks.Trace(&mScriptObject, "mScriptObject", aClosure);
+ }
+ }
+
+ nsCOMPtr<nsIURI> mSrcURI;
+ uint32_t mLineNo;
+ bool mSrcLoading;
+ bool mOutOfLine;
+ mozilla::dom::XULDocument* mSrcLoadWaiters; // [OWNER] but not COMPtr
+ uint32_t mLangVersion;
+private:
+ JS::Heap<JSScript*> mScriptObject;
+};
+
+class nsXULPrototypeText : public nsXULPrototypeNode
+{
+public:
+ nsXULPrototypeText()
+ : nsXULPrototypeNode(eType_Text)
+ {
+ }
+
+ virtual ~nsXULPrototypeText()
+ {
+ }
+
+ virtual nsresult Serialize(nsIObjectOutputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) override;
+ virtual nsresult Deserialize(nsIObjectInputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc,
+ nsIURI* aDocumentURI,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) override;
+
+ nsString mValue;
+};
+
+class nsXULPrototypePI : public nsXULPrototypeNode
+{
+public:
+ nsXULPrototypePI()
+ : nsXULPrototypeNode(eType_PI)
+ {
+ }
+
+ virtual ~nsXULPrototypePI()
+ {
+ }
+
+ virtual nsresult Serialize(nsIObjectOutputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) override;
+ virtual nsresult Deserialize(nsIObjectInputStream* aStream,
+ nsXULPrototypeDocument* aProtoDoc,
+ nsIURI* aDocumentURI,
+ const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) override;
+
+ nsString mTarget;
+ nsString mData;
+};
+
+////////////////////////////////////////////////////////////////////////
+
+/**
+
+ The XUL element.
+
+ */
+
+#define XUL_ELEMENT_FLAG_BIT(n_) NODE_FLAG_BIT(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + (n_))
+
+// XUL element specific bits
+enum {
+ XUL_ELEMENT_TEMPLATE_GENERATED = XUL_ELEMENT_FLAG_BIT(0),
+ XUL_ELEMENT_HAS_CONTENTMENU_LISTENER = XUL_ELEMENT_FLAG_BIT(1),
+ XUL_ELEMENT_HAS_POPUP_LISTENER = XUL_ELEMENT_FLAG_BIT(2)
+};
+
+ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 3);
+
+#undef XUL_ELEMENT_FLAG_BIT
+
+class nsXULElement final : public nsStyledElement,
+ public nsIDOMXULElement
+{
+public:
+ using Element::Blur;
+ using Element::Focus;
+ explicit nsXULElement(already_AddRefed<mozilla::dom::NodeInfo> aNodeInfo);
+
+ static nsresult
+ Create(nsXULPrototypeElement* aPrototype, nsIDocument* aDocument,
+ bool aIsScriptable, bool aIsRoot, mozilla::dom::Element** aResult);
+
+ NS_IMPL_FROMCONTENT(nsXULElement, kNameSpaceID_XUL)
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsXULElement, nsStyledElement)
+
+ // nsINode
+ virtual nsresult PreHandleEvent(
+ mozilla::EventChainPreVisitor& aVisitor) override;
+
+ // nsIContent
+ virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers) override;
+ virtual void UnbindFromTree(bool aDeep, bool aNullParent) override;
+ virtual void RemoveChildAt(uint32_t aIndex, bool aNotify) override;
+ virtual void DestroyContent() override;
+
+#ifdef DEBUG
+ virtual void List(FILE* out, int32_t aIndent) const override;
+ virtual void DumpContent(FILE* out, int32_t aIndent,bool aDumpAll) const override
+ {
+ }
+#endif
+
+ virtual bool PerformAccesskey(bool aKeyCausesActivation,
+ bool aIsTrustedEvent) override;
+ nsresult ClickWithInputSource(uint16_t aInputSource, bool aIsTrustedEvent);
+
+ virtual nsIContent *GetBindingParent() const override;
+ virtual bool IsNodeOfType(uint32_t aFlags) const override;
+ virtual bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) override;
+
+ NS_IMETHOD WalkContentStyleRules(nsRuleWalker* aRuleWalker) override;
+ virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const override;
+ NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const override;
+
+ // XUL element methods
+ /**
+ * The template-generated flag is used to indicate that a
+ * template-generated element has already had its children generated.
+ */
+ void SetTemplateGenerated() { SetFlags(XUL_ELEMENT_TEMPLATE_GENERATED); }
+ void ClearTemplateGenerated() { UnsetFlags(XUL_ELEMENT_TEMPLATE_GENERATED); }
+ bool GetTemplateGenerated() { return HasFlag(XUL_ELEMENT_TEMPLATE_GENERATED); }
+
+ // nsIDOMNode
+ NS_FORWARD_NSIDOMNODE_TO_NSINODE
+ // And since that shadowed GetParentElement with the XPCOM
+ // signature, pull in the one we care about.
+ using nsStyledElement::GetParentElement;
+
+ // nsIDOMElement
+ NS_FORWARD_NSIDOMELEMENT_TO_GENERIC
+
+ // nsIDOMXULElement
+ NS_DECL_NSIDOMXULELEMENT
+
+ virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
+ virtual mozilla::EventStates IntrinsicState() const override;
+
+ nsresult GetFrameLoaderXPCOM(nsIFrameLoader** aFrameLoader);
+ nsresult GetParentApplication(mozIApplication** aApplication);
+ void PresetOpenerWindow(mozIDOMWindowProxy* aWindow, ErrorResult& aRv);
+ nsresult SetIsPrerendered();
+
+ virtual void RecompileScriptEventListeners() override;
+
+ // This function should ONLY be used by BindToTree implementations.
+ // The function exists solely because XUL elements store the binding
+ // parent as a member instead of in the slots, as Element does.
+ void SetXULBindingParent(nsIContent* aBindingParent)
+ {
+ mBindingParent = aBindingParent;
+ }
+
+ virtual nsIDOMNode* AsDOMNode() override { return this; }
+
+ virtual bool IsEventAttributeName(nsIAtom* aName) override;
+
+ void SetXULAttr(nsIAtom* aName, const nsAString& aValue,
+ mozilla::ErrorResult& aError)
+ {
+ aError = SetAttr(kNameSpaceID_None, aName, aValue, true);
+ }
+ void SetXULBoolAttr(nsIAtom* aName, bool aValue)
+ {
+ if (aValue) {
+ SetAttr(kNameSpaceID_None, aName, NS_LITERAL_STRING("true"), true);
+ } else {
+ UnsetAttr(kNameSpaceID_None, aName, true);
+ }
+ }
+
+ // WebIDL API
+ // The XPCOM getter is fine for our string attributes.
+ // The XPCOM setter is fine for our bool attributes.
+ void SetClassName(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::_class, aValue, rv);
+ }
+ void SetAlign(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::align, aValue, rv);
+ }
+ void SetDir(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::dir, aValue, rv);
+ }
+ void SetFlex(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::flex, aValue, rv);
+ }
+ void SetFlexGroup(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::flexgroup, aValue, rv);
+ }
+ void SetOrdinal(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::ordinal, aValue, rv);
+ }
+ void SetOrient(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::orient, aValue, rv);
+ }
+ void SetPack(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::pack, aValue, rv);
+ }
+ bool Hidden() const
+ {
+ return BoolAttrIsTrue(nsGkAtoms::hidden);
+ }
+ bool Collapsed() const
+ {
+ return BoolAttrIsTrue(nsGkAtoms::collapsed);
+ }
+ void SetObserves(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::observes, aValue, rv);
+ }
+ void SetMenu(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::menu, aValue, rv);
+ }
+ void SetContextMenu(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::contextmenu, aValue, rv);
+ }
+ void SetTooltip(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::tooltip, aValue, rv);
+ }
+ void SetWidth(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::width, aValue, rv);
+ }
+ void SetHeight(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::height, aValue, rv);
+ }
+ void SetMinWidth(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::minwidth, aValue, rv);
+ }
+ void SetMinHeight(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::minheight, aValue, rv);
+ }
+ void SetMaxWidth(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::maxwidth, aValue, rv);
+ }
+ void SetMaxHeight(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::maxheight, aValue, rv);
+ }
+ void SetPersist(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::persist, aValue, rv);
+ }
+ void SetLeft(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::left, aValue, rv);
+ }
+ void SetTop(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::top, aValue, rv);
+ }
+ void SetDatasources(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::datasources, aValue, rv);
+ }
+ void SetRef(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::ref, aValue, rv);
+ }
+ void SetTooltipText(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::tooltiptext, aValue, rv);
+ }
+ void SetStatusText(const nsAString& aValue, mozilla::ErrorResult& rv)
+ {
+ SetXULAttr(nsGkAtoms::statustext, aValue, rv);
+ }
+ bool AllowEvents() const
+ {
+ return BoolAttrIsTrue(nsGkAtoms::allowevents);
+ }
+ already_AddRefed<nsIRDFCompositeDataSource> GetDatabase();
+ already_AddRefed<nsIXULTemplateBuilder> GetBuilder();
+ already_AddRefed<nsIRDFResource> GetResource(mozilla::ErrorResult& rv);
+ nsIControllers* GetControllers(mozilla::ErrorResult& rv);
+ already_AddRefed<mozilla::dom::BoxObject> GetBoxObject(mozilla::ErrorResult& rv);
+ void Click(mozilla::ErrorResult& rv);
+ // The XPCOM DoCommand never fails, so it's OK for us.
+ already_AddRefed<nsINodeList>
+ GetElementsByAttribute(const nsAString& aAttribute,
+ const nsAString& aValue);
+ already_AddRefed<nsINodeList>
+ GetElementsByAttributeNS(const nsAString& aNamespaceURI,
+ const nsAString& aAttribute,
+ const nsAString& aValue,
+ mozilla::ErrorResult& rv);
+ // Style() inherited from nsStyledElement
+ already_AddRefed<nsFrameLoader> GetFrameLoader();
+ void InternalSetFrameLoader(nsIFrameLoader* aNewFrameLoader);
+ void SwapFrameLoaders(mozilla::dom::HTMLIFrameElement& aOtherLoaderOwner,
+ mozilla::ErrorResult& rv);
+ void SwapFrameLoaders(nsXULElement& aOtherLoaderOwner,
+ mozilla::ErrorResult& rv);
+ void SwapFrameLoaders(nsIFrameLoaderOwner* aOtherLoaderOwner,
+ mozilla::ErrorResult& rv);
+
+ nsINode* GetScopeChainParent() const override
+ {
+ // For XUL, the parent is the parent element, if any
+ Element* parent = GetParentElement();
+ return parent ? parent : nsStyledElement::GetScopeChainParent();
+ }
+
+protected:
+ ~nsXULElement();
+
+ // This can be removed if EnsureContentsGenerated dies.
+ friend class nsNSElementTearoff;
+
+ // Implementation methods
+ nsresult EnsureContentsGenerated(void) const;
+
+ nsresult ExecuteOnBroadcastHandler(nsIDOMElement* anElement, const nsAString& attrName);
+
+ static nsresult
+ ExecuteJSCode(nsIDOMElement* anElement, mozilla::WidgetEvent* aEvent);
+
+ // Helper routine that crawls a parent chain looking for a tree element.
+ NS_IMETHOD GetParentTree(nsIDOMXULMultiSelectControlElement** aTreeElement);
+
+ nsresult AddPopupListener(nsIAtom* aName);
+
+ class nsXULSlots : public mozilla::dom::Element::nsDOMSlots
+ {
+ public:
+ nsXULSlots();
+ virtual ~nsXULSlots();
+
+ void Traverse(nsCycleCollectionTraversalCallback &cb);
+
+ nsCOMPtr<nsISupports> mFrameLoaderOrOpener;
+ };
+
+ virtual nsINode::nsSlots* CreateSlots() override;
+
+ nsresult LoadSrc();
+
+ /**
+ * The nearest enclosing content node with a binding
+ * that created us. [Weak]
+ */
+ nsIContent* mBindingParent;
+
+ /**
+ * Abandon our prototype linkage, and copy all attributes locally
+ */
+ nsresult MakeHeavyweight(nsXULPrototypeElement* aPrototype);
+
+ virtual nsresult BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify) override;
+ virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify) override;
+
+ virtual void UpdateEditableState(bool aNotify) override;
+
+ virtual bool ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult) override;
+
+ virtual mozilla::EventListenerManager*
+ GetEventListenerManagerForAttr(nsIAtom* aAttrName,
+ bool* aDefer) override;
+
+ /**
+ * Add a listener for the specified attribute, if appropriate.
+ */
+ void AddListenerFor(const nsAttrName& aName,
+ bool aCompileEventHandlers);
+ void MaybeAddPopupListener(nsIAtom* aLocalName);
+
+ nsIWidget* GetWindowWidget();
+
+ // attribute setters for widget
+ nsresult HideWindowChrome(bool aShouldHide);
+ void SetChromeMargins(const nsAttrValue* aValue);
+ void ResetChromeMargins();
+ void SetTitlebarColor(nscolor aColor, bool aActive);
+
+ void SetDrawsInTitlebar(bool aState);
+ void SetDrawsTitle(bool aState);
+ void UpdateBrightTitlebarForeground(nsIDocument* aDocument);
+
+ void RemoveBroadcaster(const nsAString & broadcasterId);
+
+protected:
+ // Internal accessor. This shadows the 'Slots', and returns
+ // appropriate value.
+ nsIControllers *Controllers() {
+ nsDOMSlots* slots = GetExistingDOMSlots();
+ return slots ? slots->mControllers : nullptr;
+ }
+
+ void UnregisterAccessKey(const nsAString& aOldValue);
+ bool BoolAttrIsTrue(nsIAtom* aName) const;
+
+ friend nsresult
+ NS_NewXULElement(mozilla::dom::Element** aResult, mozilla::dom::NodeInfo *aNodeInfo);
+ friend void
+ NS_TrustedNewXULElement(nsIContent** aResult, mozilla::dom::NodeInfo *aNodeInfo);
+
+ static already_AddRefed<nsXULElement>
+ Create(nsXULPrototypeElement* aPrototype, mozilla::dom::NodeInfo *aNodeInfo,
+ bool aIsScriptable, bool aIsRoot);
+
+ bool IsReadWriteTextElement() const
+ {
+ return IsAnyOfXULElements(nsGkAtoms::textbox, nsGkAtoms::textarea) &&
+ !HasAttr(kNameSpaceID_None, nsGkAtoms::readonly);
+ }
+
+ virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ void MaybeUpdatePrivateLifetime();
+};
+
+#endif // nsXULElement_h__
diff --git a/dom/xul/nsXULPopupListener.cpp b/dom/xul/nsXULPopupListener.cpp
new file mode 100644
index 0000000000..889553c4df
--- /dev/null
+++ b/dom/xul/nsXULPopupListener.cpp
@@ -0,0 +1,442 @@
+/* -*- 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 file provides the implementation for xul popup listener which
+ tracks xul popups and context menus
+ */
+
+#include "nsXULPopupListener.h"
+#include "nsCOMPtr.h"
+#include "nsGkAtoms.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMXULElement.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMDocumentXBL.h"
+#include "nsContentCID.h"
+#include "nsContentUtils.h"
+#include "nsXULPopupManager.h"
+#include "nsIScriptContext.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMXULDocument.h"
+#include "nsIDocument.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsLayoutUtils.h"
+#include "mozilla/ReflowInput.h"
+#include "nsIObjectLoadingContent.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/FragmentOrElement.h"
+
+// for event firing in context menus
+#include "nsPresContext.h"
+#include "nsIPresShell.h"
+#include "nsFocusManager.h"
+#include "nsPIDOMWindow.h"
+#include "nsViewManager.h"
+#include "nsError.h"
+#include "nsMenuFrame.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// on win32 and os/2, context menus come up on mouse up. On other platforms,
+// they appear on mouse down. Certain bits of code care about this difference.
+#if defined(XP_WIN)
+#define NS_CONTEXT_MENU_IS_MOUSEUP 1
+#endif
+
+nsXULPopupListener::nsXULPopupListener(mozilla::dom::Element* aElement,
+ bool aIsContext)
+ : mElement(aElement), mPopupContent(nullptr), mIsContext(aIsContext)
+{
+}
+
+nsXULPopupListener::~nsXULPopupListener(void)
+{
+ ClosePopup();
+}
+
+NS_IMPL_CYCLE_COLLECTION(nsXULPopupListener, mElement, mPopupContent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULPopupListener)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULPopupListener)
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsXULPopupListener)
+ // If the owner, mElement, can be skipped, so can we.
+ if (tmp->mElement) {
+ return mozilla::dom::FragmentOrElement::CanSkip(tmp->mElement, true);
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsXULPopupListener)
+ if (tmp->mElement) {
+ return mozilla::dom::FragmentOrElement::CanSkipInCC(tmp->mElement);
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsXULPopupListener)
+ if (tmp->mElement) {
+ return mozilla::dom::FragmentOrElement::CanSkipThis(tmp->mElement);
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULPopupListener)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+////////////////////////////////////////////////////////////////
+// nsIDOMEventListener
+
+nsresult
+nsXULPopupListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+
+ if(!((eventType.EqualsLiteral("mousedown") && !mIsContext) ||
+ (eventType.EqualsLiteral("contextmenu") && mIsContext)))
+ return NS_OK;
+
+ int16_t button;
+
+ nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
+ if (!mouseEvent) {
+ //non-ui event passed in. bad things.
+ return NS_OK;
+ }
+
+ // Get the node that was clicked on.
+ EventTarget* target = mouseEvent->AsEvent()->InternalDOMEvent()->GetTarget();
+ nsCOMPtr<nsIDOMNode> targetNode = do_QueryInterface(target);
+
+ if (!targetNode && mIsContext) {
+ // Not a DOM node, see if it's the DOM window (bug 380818).
+ nsCOMPtr<nsPIDOMWindowInner> domWin = do_QueryInterface(target);
+ if (!domWin) {
+ return NS_ERROR_DOM_WRONG_TYPE_ERR;
+ }
+ // Try to use the root node as target node.
+ nsCOMPtr<nsIDocument> doc = domWin->GetDoc();
+
+ if (doc)
+ targetNode = do_QueryInterface(doc->GetRootElement());
+ if (!targetNode) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target);
+ if (!targetContent) {
+ return NS_OK;
+ }
+ if (EventStateManager::IsRemoteTarget(targetContent)) {
+ return NS_OK;
+ }
+
+ bool preventDefault;
+ mouseEvent->AsEvent()->GetDefaultPrevented(&preventDefault);
+ if (preventDefault && targetNode && mIsContext) {
+ // Someone called preventDefault on a context menu.
+ // Let's make sure they are allowed to do so.
+ bool eventEnabled =
+ Preferences::GetBool("dom.event.contextmenu.enabled", true);
+ if (!eventEnabled) {
+ // If the target node is for plug-in, we should not open XUL context
+ // menu on windowless plug-ins.
+ nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(targetNode);
+ uint32_t type;
+ if (olc && NS_SUCCEEDED(olc->GetDisplayedType(&type)) &&
+ type == nsIObjectLoadingContent::TYPE_PLUGIN) {
+ return NS_OK;
+ }
+
+ // The user wants his contextmenus. Let's make sure that this is a website
+ // and not chrome since there could be places in chrome which don't want
+ // contextmenus.
+ nsCOMPtr<nsINode> node = do_QueryInterface(targetNode);
+ if (node) {
+ nsCOMPtr<nsIPrincipal> system;
+ nsContentUtils::GetSecurityManager()->
+ GetSystemPrincipal(getter_AddRefs(system));
+ if (node->NodePrincipal() != system) {
+ // This isn't chrome. Cancel the preventDefault() and
+ // let the event go forth.
+ preventDefault = false;
+ }
+ }
+ }
+ }
+
+ if (preventDefault) {
+ // someone called preventDefault. bail.
+ return NS_OK;
+ }
+
+ // prevent popups on menu and menuitems as they handle their own popups
+ // This was added for bug 96920.
+ // If a menu item child was clicked on that leads to a popup needing
+ // to show, we know (guaranteed) that we're dealing with a menu or
+ // submenu of an already-showing popup. We don't need to do anything at all.
+ if (!mIsContext) {
+ if (targetContent &&
+ targetContent->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menuitem))
+ return NS_OK;
+ }
+
+ if (mIsContext) {
+#ifndef NS_CONTEXT_MENU_IS_MOUSEUP
+ uint16_t inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
+ mouseEvent->GetMozInputSource(&inputSource);
+ bool isTouch = inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
+ // If the context menu launches on mousedown,
+ // we have to fire focus on the content we clicked on
+ FireFocusOnTargetContent(targetNode, isTouch);
+#endif
+ }
+ else {
+ // Only open popups when the left mouse button is down.
+ mouseEvent->GetButton(&button);
+ if (button != 0)
+ return NS_OK;
+ }
+
+ // Open the popup. LaunchPopup will call StopPropagation and PreventDefault
+ // in the right situations.
+ LaunchPopup(aEvent, targetContent);
+
+ return NS_OK;
+}
+
+#ifndef NS_CONTEXT_MENU_IS_MOUSEUP
+nsresult
+nsXULPopupListener::FireFocusOnTargetContent(nsIDOMNode* aTargetNode, bool aIsTouch)
+{
+ nsresult rv;
+ nsCOMPtr<nsIDOMDocument> domDoc;
+ rv = aTargetNode->GetOwnerDocument(getter_AddRefs(domDoc));
+ if(NS_SUCCEEDED(rv) && domDoc)
+ {
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
+
+ // Get nsIDOMElement for targetNode
+ nsIPresShell *shell = doc->GetShell();
+ if (!shell)
+ return NS_ERROR_FAILURE;
+
+ // strong reference to keep this from going away between events
+ // XXXbz between what events? We don't use this local at all!
+ RefPtr<nsPresContext> context = shell->GetPresContext();
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aTargetNode);
+ nsIFrame* targetFrame = content->GetPrimaryFrame();
+ if (!targetFrame) return NS_ERROR_FAILURE;
+
+ const nsStyleUserInterface* ui = targetFrame->StyleUserInterface();
+ bool suppressBlur = (ui->mUserFocus == StyleUserFocus::Ignore);
+
+ nsCOMPtr<nsIDOMElement> element;
+ nsCOMPtr<nsIContent> newFocus = do_QueryInterface(content);
+
+ nsIFrame* currFrame = targetFrame;
+ // Look for the nearest enclosing focusable frame.
+ while (currFrame) {
+ int32_t tabIndexUnused;
+ if (currFrame->IsFocusable(&tabIndexUnused, true)) {
+ newFocus = currFrame->GetContent();
+ nsCOMPtr<nsIDOMElement> domElement(do_QueryInterface(newFocus));
+ if (domElement) {
+ element = domElement;
+ break;
+ }
+ }
+ currFrame = currFrame->GetParent();
+ }
+
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ if (element) {
+ uint32_t focusFlags = nsIFocusManager::FLAG_BYMOUSE |
+ nsIFocusManager::FLAG_NOSCROLL;
+ if (aIsTouch) {
+ focusFlags |= nsIFocusManager::FLAG_BYTOUCH;
+ }
+ fm->SetFocus(element, focusFlags);
+ } else if (!suppressBlur) {
+ nsPIDOMWindowOuter *window = doc->GetWindow();
+ fm->ClearFocus(window);
+ }
+ }
+
+ EventStateManager* esm = context->EventStateManager();
+ nsCOMPtr<nsIContent> focusableContent = do_QueryInterface(element);
+ esm->SetContentState(focusableContent, NS_EVENT_STATE_ACTIVE);
+ }
+ return rv;
+}
+#endif
+
+// ClosePopup
+//
+// Do everything needed to shut down the popup.
+//
+// NOTE: This routine is safe to call even if the popup is already closed.
+//
+void
+nsXULPopupListener::ClosePopup()
+{
+ if (mPopupContent) {
+ // this is called when the listener is going away, so make sure that the
+ // popup is hidden. Use asynchronous hiding just to be safe so we don't
+ // fire events during destruction.
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm)
+ pm->HidePopup(mPopupContent, false, true, true, false);
+ mPopupContent = nullptr; // release the popup
+ }
+} // ClosePopup
+
+static already_AddRefed<nsIContent>
+GetImmediateChild(nsIContent* aContent, nsIAtom *aTag)
+{
+ for (nsIContent* child = aContent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child->IsXULElement(aTag)) {
+ nsCOMPtr<nsIContent> ret = child;
+ return ret.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+//
+// LaunchPopup
+//
+// Given the element on which the event was triggered and the mouse locations in
+// Client and widget coordinates, popup a new window showing the appropriate
+// content.
+//
+// aTargetContent is the target of the mouse event aEvent that triggered the
+// popup. mElement is the element that the popup menu is attached to.
+// aTargetContent may be equal to mElement or it may be a descendant.
+//
+// This looks for an attribute on |mElement| of the appropriate popup type
+// (popup, context) and uses that attribute's value as an ID for
+// the popup content in the document.
+//
+nsresult
+nsXULPopupListener::LaunchPopup(nsIDOMEvent* aEvent, nsIContent* aTargetContent)
+{
+ nsresult rv = NS_OK;
+
+ nsAutoString identifier;
+ nsIAtom* type = mIsContext ? nsGkAtoms::context : nsGkAtoms::popup;
+ bool hasPopupAttr = mElement->GetAttr(kNameSpaceID_None, type, identifier);
+
+ if (identifier.IsEmpty()) {
+ hasPopupAttr = mElement->GetAttr(kNameSpaceID_None,
+ mIsContext ? nsGkAtoms::contextmenu : nsGkAtoms::menu,
+ identifier) || hasPopupAttr;
+ }
+
+ if (hasPopupAttr) {
+ aEvent->StopPropagation();
+ aEvent->PreventDefault();
+ }
+
+ if (identifier.IsEmpty())
+ return rv;
+
+ // Try to find the popup content and the document.
+ nsCOMPtr<nsIDocument> document = mElement->GetComposedDoc();
+ if (!document) {
+ NS_WARNING("No document!");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Handle the _child case for popups and context menus
+ nsCOMPtr<nsIContent> popup;
+ if (identifier.EqualsLiteral("_child")) {
+ popup = GetImmediateChild(mElement, nsGkAtoms::menupopup);
+ if (!popup) {
+ nsCOMPtr<nsIDOMDocumentXBL> nsDoc(do_QueryInterface(document));
+ nsCOMPtr<nsIDOMNodeList> list;
+ nsCOMPtr<nsIDOMElement> el = do_QueryInterface(mElement);
+ nsDoc->GetAnonymousNodes(el, getter_AddRefs(list));
+ if (list) {
+ uint32_t ctr,listLength;
+ nsCOMPtr<nsIDOMNode> node;
+ list->GetLength(&listLength);
+ for (ctr = 0; ctr < listLength; ctr++) {
+ list->Item(ctr, getter_AddRefs(node));
+ nsCOMPtr<nsIContent> childContent(do_QueryInterface(node));
+
+ if (childContent->NodeInfo()->Equals(nsGkAtoms::menupopup,
+ kNameSpaceID_XUL)) {
+ popup.swap(childContent);
+ break;
+ }
+ }
+ }
+ }
+ } else if (!mElement->IsInUncomposedDoc() ||
+ !(popup = document->GetElementById(identifier))) {
+ // XXXsmaug Should we try to use ShadowRoot::GetElementById in case
+ // mElement is in shadow DOM?
+ //
+ // Use getElementById to obtain the popup content and gracefully fail if
+ // we didn't find any popup content in the document.
+ NS_WARNING("GetElementById had some kind of spasm.");
+ return rv;
+ }
+
+ // return if no popup was found or the popup is the element itself.
+ if (!popup || popup == mElement)
+ return NS_OK;
+
+ // Submenus can't be used as context menus or popups, bug 288763.
+ // Similar code also in nsXULTooltipListener::GetTooltipFor.
+ nsIContent* parent = popup->GetParent();
+ if (parent) {
+ nsMenuFrame* menu = do_QueryFrame(parent->GetPrimaryFrame());
+ if (menu)
+ return NS_OK;
+ }
+
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (!pm)
+ return NS_OK;
+
+ // For left-clicks, if the popup has an position attribute, or both the
+ // popupanchor and popupalign attributes are used, anchor the popup to the
+ // element, otherwise just open it at the screen position where the mouse
+ // was clicked. Context menus always open at the mouse position.
+ mPopupContent = popup;
+ if (!mIsContext &&
+ (mPopupContent->HasAttr(kNameSpaceID_None, nsGkAtoms::position) ||
+ (mPopupContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popupanchor) &&
+ mPopupContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popupalign)))) {
+ pm->ShowPopup(mPopupContent, mElement, EmptyString(), 0, 0,
+ false, true, false, aEvent);
+ }
+ else {
+ int32_t xPos = 0, yPos = 0;
+ nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
+ mouseEvent->GetScreenX(&xPos);
+ mouseEvent->GetScreenY(&yPos);
+
+ pm->ShowPopupAtScreen(mPopupContent, xPos, yPos, mIsContext, aEvent);
+ }
+
+ return NS_OK;
+}
diff --git a/dom/xul/nsXULPopupListener.h b/dom/xul/nsXULPopupListener.h
new file mode 100644
index 0000000000..6ac0e0e544
--- /dev/null
+++ b/dom/xul/nsXULPopupListener.h
@@ -0,0 +1,61 @@
+/* -*- 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 is the popup listener implementation for popup menus and context menus.
+ */
+
+#ifndef nsXULPopupListener_h___
+#define nsXULPopupListener_h___
+
+#include "nsCOMPtr.h"
+
+#include "mozilla/dom/Element.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsIDOMEventListener.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsXULPopupListener : public nsIDOMEventListener
+{
+public:
+ // aElement is the element that the popup is attached to. If aIsContext is
+ // false, the popup opens on left click on aElement or a descendant. If
+ // aIsContext is true, the popup is a context menu which opens on a
+ // context menu event.
+ nsXULPopupListener(mozilla::dom::Element* aElement, bool aIsContext);
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_CLASS(nsXULPopupListener)
+ NS_DECL_NSIDOMEVENTLISTENER
+
+protected:
+ virtual ~nsXULPopupListener(void);
+
+ // open the popup. aEvent is the event that triggered the popup such as
+ // a mouse click and aTargetContent is the target of this event.
+ virtual nsresult LaunchPopup(nsIDOMEvent* aEvent, nsIContent* aTargetContent);
+
+ // close the popup when the listener goes away
+ virtual void ClosePopup();
+
+private:
+#ifndef NS_CONTEXT_MENU_IS_MOUSEUP
+ // When a context menu is opened, focus the target of the contextmenu event.
+ nsresult FireFocusOnTargetContent(nsIDOMNode* aTargetNode, bool aIsTouch);
+#endif
+
+ // |mElement| is the node to which this listener is attached.
+ nsCOMPtr<mozilla::dom::Element> mElement;
+
+ // The popup that is getting shown on top of mElement.
+ nsCOMPtr<nsIContent> mPopupContent;
+
+ // true if a context popup
+ bool mIsContext;
+};
+
+#endif // nsXULPopupListener_h___
diff --git a/dom/xul/nsXULPrototypeCache.cpp b/dom/xul/nsXULPrototypeCache.cpp
new file mode 100644
index 0000000000..84a201d594
--- /dev/null
+++ b/dom/xul/nsXULPrototypeCache.cpp
@@ -0,0 +1,599 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsXULPrototypeCache.h"
+
+#include "plstr.h"
+#include "nsXULPrototypeDocument.h"
+#include "nsIServiceManager.h"
+#include "nsIURI.h"
+
+#include "nsIChromeRegistry.h"
+#include "nsIFile.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIObserverService.h"
+#include "nsIStringStream.h"
+#include "nsIStorageStream.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+
+#include "js/TracingAPI.h"
+
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/scache/StartupCache.h"
+#include "mozilla/scache/StartupCacheUtils.h"
+#include "mozilla/Telemetry.h"
+
+using namespace mozilla;
+using namespace mozilla::scache;
+
+static bool gDisableXULCache = false; // enabled by default
+static const char kDisableXULCachePref[] = "nglayout.debug.disable_xul_cache";
+static const char kXULCacheInfoKey[] = "nsXULPrototypeCache.startupCache";
+static const char kXULCachePrefix[] = "xulcache";
+
+//----------------------------------------------------------------------
+
+static void
+UpdategDisableXULCache()
+{
+ // Get the value of "nglayout.debug.disable_xul_cache" preference
+ gDisableXULCache =
+ Preferences::GetBool(kDisableXULCachePref, gDisableXULCache);
+
+ // Sets the flag if the XUL cache is disabled
+ if (gDisableXULCache) {
+ Telemetry::Accumulate(Telemetry::XUL_CACHE_DISABLED, true);
+ }
+
+}
+
+static void
+DisableXULCacheChangedCallback(const char* aPref, void* aClosure)
+{
+ bool wasEnabled = !gDisableXULCache;
+ UpdategDisableXULCache();
+
+ if (wasEnabled && gDisableXULCache) {
+ nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
+ if (cache) {
+ // AbortCaching() calls Flush() for us.
+ cache->AbortCaching();
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+
+nsXULPrototypeCache* nsXULPrototypeCache::sInstance = nullptr;
+
+
+nsXULPrototypeCache::nsXULPrototypeCache()
+{
+}
+
+
+nsXULPrototypeCache::~nsXULPrototypeCache()
+{
+ FlushScripts();
+}
+
+
+NS_IMPL_ISUPPORTS(nsXULPrototypeCache, nsIObserver)
+
+/* static */ nsXULPrototypeCache*
+nsXULPrototypeCache::GetInstance()
+{
+ if (!sInstance) {
+ NS_ADDREF(sInstance = new nsXULPrototypeCache());
+
+ UpdategDisableXULCache();
+
+ Preferences::RegisterCallback(DisableXULCacheChangedCallback,
+ kDisableXULCachePref);
+
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ if (obsSvc) {
+ nsXULPrototypeCache *p = sInstance;
+ obsSvc->AddObserver(p, "chrome-flush-skin-caches", false);
+ obsSvc->AddObserver(p, "chrome-flush-caches", false);
+ obsSvc->AddObserver(p, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ obsSvc->AddObserver(p, "startupcache-invalidate", false);
+ }
+
+ }
+ return sInstance;
+}
+
+//----------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsXULPrototypeCache::Observe(nsISupports* aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ if (!strcmp(aTopic, "chrome-flush-skin-caches")) {
+ FlushSkinFiles();
+ }
+ else if (!strcmp(aTopic, "chrome-flush-caches") ||
+ !strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ Flush();
+ }
+ else if (!strcmp(aTopic, "startupcache-invalidate")) {
+ AbortCaching();
+ }
+ else {
+ NS_WARNING("Unexpected observer topic.");
+ }
+ return NS_OK;
+}
+
+nsXULPrototypeDocument*
+nsXULPrototypeCache::GetPrototype(nsIURI* aURI)
+{
+ if (!aURI)
+ return nullptr;
+
+ nsCOMPtr<nsIURI> uriWithoutRef;
+ aURI->CloneIgnoringRef(getter_AddRefs(uriWithoutRef));
+
+ nsXULPrototypeDocument* protoDoc = mPrototypeTable.GetWeak(uriWithoutRef);
+ if (protoDoc)
+ return protoDoc;
+
+ nsresult rv = BeginCaching(aURI);
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ // No prototype in XUL memory cache. Spin up the cache Service.
+ nsCOMPtr<nsIObjectInputStream> ois;
+ rv = GetInputStream(aURI, getter_AddRefs(ois));
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ RefPtr<nsXULPrototypeDocument> newProto;
+ rv = NS_NewXULPrototypeDocument(getter_AddRefs(newProto));
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ rv = newProto->Read(ois);
+ if (NS_SUCCEEDED(rv)) {
+ rv = PutPrototype(newProto);
+ } else {
+ newProto = nullptr;
+ }
+
+ mInputStreamTable.Remove(aURI);
+ return newProto;
+}
+
+nsresult
+nsXULPrototypeCache::PutPrototype(nsXULPrototypeDocument* aDocument)
+{
+ if (!aDocument->GetURI()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ aDocument->GetURI()->CloneIgnoringRef(getter_AddRefs(uri));
+
+ // Put() releases any old value and addrefs the new one
+ mPrototypeTable.Put(uri, aDocument);
+
+ return NS_OK;
+}
+
+nsresult
+nsXULPrototypeCache::PutStyleSheet(CSSStyleSheet* aStyleSheet)
+{
+ nsIURI* uri = aStyleSheet->GetSheetURI();
+
+ mStyleSheetTable.Put(uri, aStyleSheet);
+
+ return NS_OK;
+}
+
+
+JSScript*
+nsXULPrototypeCache::GetScript(nsIURI* aURI)
+{
+ return mScriptTable.Get(aURI);
+}
+
+nsresult
+nsXULPrototypeCache::PutScript(nsIURI* aURI,
+ JS::Handle<JSScript*> aScriptObject)
+{
+ MOZ_ASSERT(aScriptObject, "Need a non-NULL script");
+
+#ifdef DEBUG_BUG_392650
+ if (mScriptTable.Get(aURI)) {
+ nsAutoCString scriptName;
+ aURI->GetSpec(scriptName);
+ nsAutoCString message("Loaded script ");
+ message += scriptName;
+ message += " twice (bug 392650)";
+ NS_WARNING(message.get());
+ }
+#endif
+
+ mScriptTable.Put(aURI, aScriptObject);
+
+ return NS_OK;
+}
+
+nsresult
+nsXULPrototypeCache::PutXBLDocumentInfo(nsXBLDocumentInfo* aDocumentInfo)
+{
+ nsIURI* uri = aDocumentInfo->DocumentURI();
+
+ RefPtr<nsXBLDocumentInfo> info;
+ mXBLDocTable.Get(uri, getter_AddRefs(info));
+ if (!info) {
+ mXBLDocTable.Put(uri, aDocumentInfo);
+ }
+ return NS_OK;
+}
+
+void
+nsXULPrototypeCache::FlushSkinFiles()
+{
+ // Flush out skin XBL files from the cache.
+ for (auto iter = mXBLDocTable.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoCString str;
+ iter.Key()->GetPath(str);
+ if (strncmp(str.get(), "/skin", 5) == 0) {
+ iter.Remove();
+ }
+ }
+
+ // Now flush out our skin stylesheets from the cache.
+ for (auto iter = mStyleSheetTable.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoCString str;
+ iter.Data()->GetSheetURI()->GetPath(str);
+ if (strncmp(str.get(), "/skin", 5) == 0) {
+ iter.Remove();
+ }
+ }
+
+ // Iterate over all the remaining XBL and make sure cached
+ // scoped skin stylesheets are flushed and refetched by the
+ // prototype bindings.
+ for (auto iter = mXBLDocTable.Iter(); !iter.Done(); iter.Next()) {
+ iter.Data()->FlushSkinStylesheets();
+ }
+}
+
+void
+nsXULPrototypeCache::FlushScripts()
+{
+ mScriptTable.Clear();
+}
+
+void
+nsXULPrototypeCache::Flush()
+{
+ mPrototypeTable.Clear();
+ mScriptTable.Clear();
+ mStyleSheetTable.Clear();
+ mXBLDocTable.Clear();
+}
+
+
+bool
+nsXULPrototypeCache::IsEnabled()
+{
+ return !gDisableXULCache;
+}
+
+void
+nsXULPrototypeCache::AbortCaching()
+{
+#ifdef DEBUG_brendan
+ NS_BREAK();
+#endif
+
+ // Flush the XUL cache for good measure, in case we cached a bogus/downrev
+ // script, somehow.
+ Flush();
+
+ // Clear the cache set
+ mStartupCacheURITable.Clear();
+}
+
+
+nsresult
+nsXULPrototypeCache::WritePrototype(nsXULPrototypeDocument* aPrototypeDocument)
+{
+ nsresult rv = NS_OK, rv2 = NS_OK;
+
+ if (!StartupCache::GetSingleton())
+ return NS_OK;
+
+ nsCOMPtr<nsIURI> protoURI = aPrototypeDocument->GetURI();
+
+ nsCOMPtr<nsIObjectOutputStream> oos;
+ rv = GetOutputStream(protoURI, getter_AddRefs(oos));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aPrototypeDocument->Write(oos);
+ NS_ENSURE_SUCCESS(rv, rv);
+ FinishOutputStream(protoURI);
+ return NS_FAILED(rv) ? rv : rv2;
+}
+
+nsresult
+nsXULPrototypeCache::GetInputStream(nsIURI* uri, nsIObjectInputStream** stream)
+{
+ nsAutoCString spec(kXULCachePrefix);
+ nsresult rv = PathifyURI(uri, spec);
+ if (NS_FAILED(rv))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ UniquePtr<char[]> buf;
+ uint32_t len;
+ nsCOMPtr<nsIObjectInputStream> ois;
+ StartupCache* sc = StartupCache::GetSingleton();
+ if (!sc)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ rv = sc->GetBuffer(spec.get(), &buf, &len);
+ if (NS_FAILED(rv))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ rv = NewObjectInputStreamFromBuffer(Move(buf), len, getter_AddRefs(ois));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInputStreamTable.Put(uri, ois);
+
+ ois.forget(stream);
+ return NS_OK;
+}
+
+nsresult
+nsXULPrototypeCache::FinishInputStream(nsIURI* uri) {
+ mInputStreamTable.Remove(uri);
+ return NS_OK;
+}
+
+nsresult
+nsXULPrototypeCache::GetOutputStream(nsIURI* uri, nsIObjectOutputStream** stream)
+{
+ nsresult rv;
+ nsCOMPtr<nsIObjectOutputStream> objectOutput;
+ nsCOMPtr<nsIStorageStream> storageStream;
+ bool found = mOutputStreamTable.Get(uri, getter_AddRefs(storageStream));
+ if (found) {
+ objectOutput = do_CreateInstance("mozilla.org/binaryoutputstream;1");
+ if (!objectOutput) return NS_ERROR_OUT_OF_MEMORY;
+ nsCOMPtr<nsIOutputStream> outputStream
+ = do_QueryInterface(storageStream);
+ objectOutput->SetOutputStream(outputStream);
+ } else {
+ rv = NewObjectOutputWrappedStorageStream(getter_AddRefs(objectOutput),
+ getter_AddRefs(storageStream),
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mOutputStreamTable.Put(uri, storageStream);
+ }
+ objectOutput.forget(stream);
+ return NS_OK;
+}
+
+nsresult
+nsXULPrototypeCache::FinishOutputStream(nsIURI* uri)
+{
+ nsresult rv;
+ StartupCache* sc = StartupCache::GetSingleton();
+ if (!sc)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsIStorageStream> storageStream;
+ bool found = mOutputStreamTable.Get(uri, getter_AddRefs(storageStream));
+ if (!found)
+ return NS_ERROR_UNEXPECTED;
+ nsCOMPtr<nsIOutputStream> outputStream
+ = do_QueryInterface(storageStream);
+ outputStream->Close();
+
+ UniquePtr<char[]> buf;
+ uint32_t len;
+ rv = NewBufferFromStorageStream(storageStream, &buf, &len);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mStartupCacheURITable.GetEntry(uri)) {
+ nsAutoCString spec(kXULCachePrefix);
+ rv = PathifyURI(uri, spec);
+ if (NS_FAILED(rv))
+ return NS_ERROR_NOT_AVAILABLE;
+ rv = sc->PutBuffer(spec.get(), buf.get(), len);
+ if (NS_SUCCEEDED(rv)) {
+ mOutputStreamTable.Remove(uri);
+ mStartupCacheURITable.PutEntry(uri);
+ }
+ }
+
+ return rv;
+}
+
+// We have data if we're in the middle of writing it or we already
+// have it in the cache.
+nsresult
+nsXULPrototypeCache::HasData(nsIURI* uri, bool* exists)
+{
+ if (mOutputStreamTable.Get(uri, nullptr)) {
+ *exists = true;
+ return NS_OK;
+ }
+ nsAutoCString spec(kXULCachePrefix);
+ nsresult rv = PathifyURI(uri, spec);
+ if (NS_FAILED(rv)) {
+ *exists = false;
+ return NS_OK;
+ }
+ UniquePtr<char[]> buf;
+ uint32_t len;
+ StartupCache* sc = StartupCache::GetSingleton();
+ if (sc) {
+ rv = sc->GetBuffer(spec.get(), &buf, &len);
+ } else {
+ *exists = false;
+ return NS_OK;
+ }
+ *exists = NS_SUCCEEDED(rv);
+ return NS_OK;
+}
+
+nsresult
+nsXULPrototypeCache::BeginCaching(nsIURI* aURI)
+{
+ nsresult rv, tmp;
+
+ nsAutoCString path;
+ aURI->GetPath(path);
+ if (!StringEndsWith(path, NS_LITERAL_CSTRING(".xul")))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ StartupCache* startupCache = StartupCache::GetSingleton();
+ if (!startupCache)
+ return NS_ERROR_FAILURE;
+
+ if (gDisableXULCache)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // Get the chrome directory to validate against the one stored in the
+ // cache file, or to store there if we're generating a new file.
+ nsCOMPtr<nsIFile> chromeDir;
+ rv = NS_GetSpecialDirectory(NS_APP_CHROME_DIR, getter_AddRefs(chromeDir));
+ if (NS_FAILED(rv))
+ return rv;
+ nsAutoCString chromePath;
+ rv = chromeDir->GetNativePath(chromePath);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // XXXbe we assume the first package's locale is the same as the locale of
+ // all subsequent packages of cached chrome URIs....
+ nsAutoCString package;
+ rv = aURI->GetHost(package);
+ if (NS_FAILED(rv))
+ return rv;
+ nsCOMPtr<nsIXULChromeRegistry> chromeReg
+ = do_GetService(NS_CHROMEREGISTRY_CONTRACTID, &rv);
+ nsAutoCString locale;
+ rv = chromeReg->GetSelectedLocale(package, false, locale);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString fileChromePath, fileLocale;
+
+ UniquePtr<char[]> buf;
+ uint32_t len, amtRead;
+ nsCOMPtr<nsIObjectInputStream> objectInput;
+
+ rv = startupCache->GetBuffer(kXULCacheInfoKey, &buf, &len);
+ if (NS_SUCCEEDED(rv))
+ rv = NewObjectInputStreamFromBuffer(Move(buf), len,
+ getter_AddRefs(objectInput));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = objectInput->ReadCString(fileLocale);
+ tmp = objectInput->ReadCString(fileChromePath);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ if (NS_FAILED(rv) ||
+ (!fileChromePath.Equals(chromePath) ||
+ !fileLocale.Equals(locale))) {
+ // Our cache won't be valid in this case, we'll need to rewrite.
+ // XXX This blows away work that other consumers (like
+ // mozJSComponentLoader) have done, need more fine-grained control.
+ startupCache->InvalidateCache();
+ mStartupCacheURITable.Clear();
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ } else if (rv != NS_ERROR_NOT_AVAILABLE)
+ // NS_ERROR_NOT_AVAILABLE is normal, usually if there's no cachefile.
+ return rv;
+
+ if (NS_FAILED(rv)) {
+ // Either the cache entry was invalid or it didn't exist, so write it now.
+ nsCOMPtr<nsIObjectOutputStream> objectOutput;
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCOMPtr<nsIStorageStream> storageStream;
+ rv = NewObjectOutputWrappedStorageStream(getter_AddRefs(objectOutput),
+ getter_AddRefs(storageStream),
+ false);
+ if (NS_SUCCEEDED(rv)) {
+ rv = objectOutput->WriteStringZ(locale.get());
+ tmp = objectOutput->WriteStringZ(chromePath.get());
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = objectOutput->Close();
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = storageStream->NewInputStream(0, getter_AddRefs(inputStream));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ uint64_t len64;
+ rv = inputStream->Available(&len64);
+ if (NS_SUCCEEDED(rv)) {
+ if (len64 <= UINT32_MAX)
+ len = (uint32_t)len64;
+ else
+ rv = NS_ERROR_FILE_TOO_BIG;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ buf = MakeUnique<char[]>(len);
+ rv = inputStream->Read(buf.get(), len, &amtRead);
+ if (NS_SUCCEEDED(rv) && len == amtRead)
+ rv = startupCache->PutBuffer(kXULCacheInfoKey, buf.get(), len);
+ else {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ // Failed again, just bail.
+ if (NS_FAILED(rv)) {
+ startupCache->InvalidateCache();
+ mStartupCacheURITable.Clear();
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsXULPrototypeCache::MarkInCCGeneration(uint32_t aGeneration)
+{
+ for (auto iter = mXBLDocTable.Iter(); !iter.Done(); iter.Next()) {
+ iter.Data()->MarkInCCGeneration(aGeneration);
+ }
+ for (auto iter = mPrototypeTable.Iter(); !iter.Done(); iter.Next()) {
+ iter.Data()->MarkInCCGeneration(aGeneration);
+ }
+}
+
+void
+nsXULPrototypeCache::MarkInGC(JSTracer* aTrc)
+{
+ for (auto iter = mScriptTable.Iter(); !iter.Done(); iter.Next()) {
+ JS::Heap<JSScript*>& script = iter.Data();
+ JS::TraceEdge(aTrc, &script, "nsXULPrototypeCache script");
+ }
+}
diff --git a/dom/xul/nsXULPrototypeCache.h b/dom/xul/nsXULPrototypeCache.h
new file mode 100644
index 0000000000..f191c57ed5
--- /dev/null
+++ b/dom/xul/nsXULPrototypeCache.h
@@ -0,0 +1,140 @@
+/* -*- 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 nsXULPrototypeCache_h__
+#define nsXULPrototypeCache_h__
+
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsXBLDocumentInfo.h"
+#include "nsJSThingHashtable.h"
+#include "nsInterfaceHashtable.h"
+#include "nsRefPtrHashtable.h"
+#include "nsURIHashKey.h"
+#include "nsXULPrototypeDocument.h"
+#include "nsIInputStream.h"
+#include "nsIStorageStream.h"
+
+#include "mozilla/scache/StartupCache.h"
+
+namespace mozilla {
+class CSSStyleSheet;
+} // namespace mozilla
+
+/**
+ * The XUL prototype cache can be used to store and retrieve shared data for
+ * XUL documents, style sheets, XBL, and scripts.
+ *
+ * The cache has two levels:
+ * 1. In-memory hashtables
+ * 2. The on-disk cache file.
+ */
+class nsXULPrototypeCache : public nsIObserver
+{
+public:
+ // nsISupports
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ bool IsCached(nsIURI* aURI) {
+ return GetPrototype(aURI) != nullptr;
+ }
+ void AbortCaching();
+
+
+ /**
+ * Whether the prototype cache is enabled.
+ */
+ bool IsEnabled();
+
+ /**
+ * Flush the cache; remove all XUL prototype documents, style
+ * sheets, and scripts.
+ */
+ void Flush();
+
+
+ // The following methods are used to put and retrive various items into and
+ // from the cache.
+
+ nsXULPrototypeDocument* GetPrototype(nsIURI* aURI);
+ nsresult PutPrototype(nsXULPrototypeDocument* aDocument);
+
+ JSScript* GetScript(nsIURI* aURI);
+ nsresult PutScript(nsIURI* aURI, JS::Handle<JSScript*> aScriptObject);
+
+ nsXBLDocumentInfo* GetXBLDocumentInfo(nsIURI* aURL) {
+ return mXBLDocTable.GetWeak(aURL);
+ }
+ nsresult PutXBLDocumentInfo(nsXBLDocumentInfo* aDocumentInfo);
+
+ /**
+ * Get a style sheet by URI. If the style sheet is not in the cache,
+ * returns nullptr.
+ */
+ mozilla::CSSStyleSheet* GetStyleSheet(nsIURI* aURI) {
+ return mStyleSheetTable.GetWeak(aURI);
+ }
+
+ /**
+ * Store a style sheet in the cache. The key, style sheet's URI is obtained
+ * from the style sheet itself.
+ */
+ nsresult PutStyleSheet(mozilla::CSSStyleSheet* aStyleSheet);
+
+ /**
+ * Write the XUL prototype document to a cache file. The proto must be
+ * fully loaded.
+ */
+ nsresult WritePrototype(nsXULPrototypeDocument* aPrototypeDocument);
+
+ /**
+ * This interface allows partial reads and writes from the buffers in the
+ * startupCache.
+ */
+ nsresult GetInputStream(nsIURI* aURI, nsIObjectInputStream** objectInput);
+ nsresult FinishInputStream(nsIURI* aURI);
+ nsresult GetOutputStream(nsIURI* aURI, nsIObjectOutputStream** objectOutput);
+ nsresult FinishOutputStream(nsIURI* aURI);
+ nsresult HasData(nsIURI* aURI, bool* exists);
+
+ static nsXULPrototypeCache* GetInstance();
+ static nsXULPrototypeCache* MaybeGetInstance() { return sInstance; }
+
+ static void ReleaseGlobals()
+ {
+ NS_IF_RELEASE(sInstance);
+ }
+
+ void MarkInCCGeneration(uint32_t aGeneration);
+ void MarkInGC(JSTracer* aTrc);
+ void FlushScripts();
+protected:
+ friend nsresult
+ NS_NewXULPrototypeCache(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+ nsXULPrototypeCache();
+ virtual ~nsXULPrototypeCache();
+
+ static nsXULPrototypeCache* sInstance;
+
+ void FlushSkinFiles();
+
+ nsRefPtrHashtable<nsURIHashKey,nsXULPrototypeDocument> mPrototypeTable; // owns the prototypes
+ nsRefPtrHashtable<nsURIHashKey,mozilla::CSSStyleSheet> mStyleSheetTable;
+ nsJSThingHashtable<nsURIHashKey, JSScript*> mScriptTable;
+ nsRefPtrHashtable<nsURIHashKey,nsXBLDocumentInfo> mXBLDocTable;
+
+ // URIs already written to the startup cache, to prevent double-caching.
+ nsTHashtable<nsURIHashKey> mStartupCacheURITable;
+
+ nsInterfaceHashtable<nsURIHashKey, nsIStorageStream> mOutputStreamTable;
+ nsInterfaceHashtable<nsURIHashKey, nsIObjectInputStream> mInputStreamTable;
+
+ // Bootstrap caching service
+ nsresult BeginCaching(nsIURI* aDocumentURI);
+};
+
+#endif // nsXULPrototypeCache_h__
diff --git a/dom/xul/nsXULPrototypeDocument.cpp b/dom/xul/nsXULPrototypeDocument.cpp
new file mode 100644
index 0000000000..01d4794dbf
--- /dev/null
+++ b/dom/xul/nsXULPrototypeDocument.cpp
@@ -0,0 +1,544 @@
+/* -*- 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 "nsXULPrototypeDocument.h"
+#include "XULDocument.h"
+
+#include "nsAString.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIPrincipal.h"
+#include "nsJSPrincipals.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIServiceManager.h"
+#include "nsIArray.h"
+#include "nsIURI.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "nsString.h"
+#include "nsIConsoleService.h"
+#include "nsIScriptError.h"
+#include "nsDOMCID.h"
+#include "nsNodeInfoManager.h"
+#include "nsContentUtils.h"
+#include "nsCCUncollectableMarker.h"
+#include "xpcpublic.h"
+#include "mozilla/dom/BindingUtils.h"
+
+using mozilla::dom::DestroyProtoAndIfaceCache;
+using mozilla::dom::XULDocument;
+
+uint32_t nsXULPrototypeDocument::gRefCnt;
+
+//----------------------------------------------------------------------
+//
+// ctors, dtors, n' stuff
+//
+
+nsXULPrototypeDocument::nsXULPrototypeDocument()
+ : mRoot(nullptr),
+ mLoaded(false),
+ mCCGeneration(0),
+ mGCNumber(0)
+{
+ ++gRefCnt;
+}
+
+
+nsresult
+nsXULPrototypeDocument::Init()
+{
+ mNodeInfoManager = new nsNodeInfoManager();
+ return mNodeInfoManager->Init(nullptr);
+}
+
+nsXULPrototypeDocument::~nsXULPrototypeDocument()
+{
+ if (mRoot)
+ mRoot->ReleaseSubtree();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULPrototypeDocument)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULPrototypeDocument)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeWaiters)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULPrototypeDocument)
+ if (nsCCUncollectableMarker::InGeneration(cb, tmp->mCCGeneration)) {
+ return NS_SUCCESS_INTERRUPTED_TRAVERSE;
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNodeInfoManager)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypeWaiters)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULPrototypeDocument)
+ NS_INTERFACE_MAP_ENTRY(nsISerializable)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULPrototypeDocument)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULPrototypeDocument)
+
+NS_IMETHODIMP
+NS_NewXULPrototypeDocument(nsXULPrototypeDocument** aResult)
+{
+ *aResult = nullptr;
+ RefPtr<nsXULPrototypeDocument> doc =
+ new nsXULPrototypeDocument();
+
+ nsresult rv = doc->Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ doc.forget(aResult);
+ return rv;
+}
+
+//----------------------------------------------------------------------
+//
+// nsISerializable methods
+//
+
+NS_IMETHODIMP
+nsXULPrototypeDocument::Read(nsIObjectInputStream* aStream)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsISupports> supports;
+ rv = aStream->ReadObject(true, getter_AddRefs(supports));
+ mURI = do_QueryInterface(supports);
+
+ uint32_t count, i;
+ nsCOMPtr<nsIURI> styleOverlayURI;
+
+ nsresult tmp = aStream->Read32(&count);
+ if (NS_FAILED(tmp)) {
+ return tmp;
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ for (i = 0; i < count; ++i) {
+ tmp = aStream->ReadObject(true, getter_AddRefs(supports));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ styleOverlayURI = do_QueryInterface(supports);
+ mStyleSheetReferences.AppendObject(styleOverlayURI);
+ }
+
+
+ // nsIPrincipal mNodeInfoManager->mPrincipal
+ nsCOMPtr<nsIPrincipal> principal;
+ tmp = aStream->ReadObject(true, getter_AddRefs(supports));
+ principal = do_QueryInterface(supports);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ // Better safe than sorry....
+ mNodeInfoManager->SetDocumentPrincipal(principal);
+
+ mRoot = new nsXULPrototypeElement();
+
+ // mozilla::dom::NodeInfo table
+ nsTArray<RefPtr<mozilla::dom::NodeInfo>> nodeInfos;
+
+ tmp = aStream->Read32(&count);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ nsAutoString namespaceURI, prefixStr, localName;
+ bool prefixIsNull;
+ nsCOMPtr<nsIAtom> prefix;
+ for (i = 0; i < count; ++i) {
+ tmp = aStream->ReadString(namespaceURI);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = aStream->ReadBoolean(&prefixIsNull);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ if (prefixIsNull) {
+ prefix = nullptr;
+ } else {
+ tmp = aStream->ReadString(prefixStr);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ prefix = NS_Atomize(prefixStr);
+ }
+ tmp = aStream->ReadString(localName);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ RefPtr<mozilla::dom::NodeInfo> nodeInfo;
+ // Using UINT16_MAX here as we don't know which nodeinfos will be
+ // used for attributes and which for elements. And that doesn't really
+ // matter.
+ tmp = mNodeInfoManager->GetNodeInfo(localName, prefix, namespaceURI,
+ UINT16_MAX,
+ getter_AddRefs(nodeInfo));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ nodeInfos.AppendElement(nodeInfo);
+ }
+
+ // Document contents
+ uint32_t type;
+ while (NS_SUCCEEDED(rv)) {
+ tmp = aStream->Read32(&type);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ if ((nsXULPrototypeNode::Type)type == nsXULPrototypeNode::eType_PI) {
+ RefPtr<nsXULPrototypePI> pi = new nsXULPrototypePI();
+
+ tmp = pi->Deserialize(aStream, this, mURI, &nodeInfos);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = AddProcessingInstruction(pi);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ } else if ((nsXULPrototypeNode::Type)type == nsXULPrototypeNode::eType_Element) {
+ tmp = mRoot->Deserialize(aStream, this, mURI, &nodeInfos);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ break;
+ } else {
+ NS_NOTREACHED("Unexpected prototype node type");
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ }
+ tmp = NotifyLoadDone();
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ return rv;
+}
+
+static nsresult
+GetNodeInfos(nsXULPrototypeElement* aPrototype,
+ nsTArray<RefPtr<mozilla::dom::NodeInfo>>& aArray)
+{
+ if (aArray.IndexOf(aPrototype->mNodeInfo) == aArray.NoIndex) {
+ aArray.AppendElement(aPrototype->mNodeInfo);
+ }
+
+ // Search attributes
+ uint32_t i;
+ for (i = 0; i < aPrototype->mNumAttributes; ++i) {
+ RefPtr<mozilla::dom::NodeInfo> ni;
+ nsAttrName* name = &aPrototype->mAttributes[i].mName;
+ if (name->IsAtom()) {
+ ni = aPrototype->mNodeInfo->NodeInfoManager()->
+ GetNodeInfo(name->Atom(), nullptr, kNameSpaceID_None,
+ nsIDOMNode::ATTRIBUTE_NODE);
+ }
+ else {
+ ni = name->NodeInfo();
+ }
+
+ if (aArray.IndexOf(ni) == aArray.NoIndex) {
+ aArray.AppendElement(ni);
+ }
+ }
+
+ // Search children
+ for (i = 0; i < aPrototype->mChildren.Length(); ++i) {
+ nsXULPrototypeNode* child = aPrototype->mChildren[i];
+ if (child->mType == nsXULPrototypeNode::eType_Element) {
+ nsresult rv =
+ GetNodeInfos(static_cast<nsXULPrototypeElement*>(child), aArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULPrototypeDocument::Write(nsIObjectOutputStream* aStream)
+{
+ nsresult rv;
+
+ rv = aStream->WriteCompoundObject(mURI, NS_GET_IID(nsIURI), true);
+
+ uint32_t count;
+
+ count = mStyleSheetReferences.Count();
+ nsresult tmp = aStream->Write32(count);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ uint32_t i;
+ for (i = 0; i < count; ++i) {
+ tmp = aStream->WriteCompoundObject(mStyleSheetReferences[i],
+ NS_GET_IID(nsIURI), true);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+
+ // nsIPrincipal mNodeInfoManager->mPrincipal
+ tmp = aStream->WriteObject(mNodeInfoManager->DocumentPrincipal(),
+ true);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+#ifdef DEBUG
+ // XXX Worrisome if we're caching things without system principal.
+ if (!nsContentUtils::IsSystemPrincipal(mNodeInfoManager->DocumentPrincipal())) {
+ NS_WARNING("Serializing document without system principal");
+ }
+#endif
+
+ // mozilla::dom::NodeInfo table
+ nsTArray<RefPtr<mozilla::dom::NodeInfo>> nodeInfos;
+ if (mRoot) {
+ tmp = GetNodeInfos(mRoot, nodeInfos);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+
+ uint32_t nodeInfoCount = nodeInfos.Length();
+ tmp = aStream->Write32(nodeInfoCount);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ for (i = 0; i < nodeInfoCount; ++i) {
+ mozilla::dom::NodeInfo *nodeInfo = nodeInfos[i];
+ NS_ENSURE_TRUE(nodeInfo, NS_ERROR_FAILURE);
+
+ nsAutoString namespaceURI;
+ nodeInfo->GetNamespaceURI(namespaceURI);
+ tmp = aStream->WriteWStringZ(namespaceURI.get());
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ nsAutoString prefix;
+ nodeInfo->GetPrefix(prefix);
+ bool nullPrefix = DOMStringIsNull(prefix);
+ tmp = aStream->WriteBoolean(nullPrefix);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ if (!nullPrefix) {
+ tmp = aStream->WriteWStringZ(prefix.get());
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+
+ nsAutoString localName;
+ nodeInfo->GetName(localName);
+ tmp = aStream->WriteWStringZ(localName.get());
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+
+ // Now serialize the document contents
+ count = mProcessingInstructions.Length();
+ for (i = 0; i < count; ++i) {
+ nsXULPrototypePI* pi = mProcessingInstructions[i];
+ tmp = pi->Serialize(aStream, this, &nodeInfos);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+
+ if (mRoot) {
+ tmp = mRoot->Serialize(aStream, this, &nodeInfos);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+
+ return rv;
+}
+
+
+//----------------------------------------------------------------------
+//
+
+nsresult
+nsXULPrototypeDocument::InitPrincipal(nsIURI* aURI, nsIPrincipal* aPrincipal)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ mURI = aURI;
+ mNodeInfoManager->SetDocumentPrincipal(aPrincipal);
+ return NS_OK;
+}
+
+
+nsIURI*
+nsXULPrototypeDocument::GetURI()
+{
+ NS_ASSERTION(mURI, "null URI");
+ return mURI;
+}
+
+
+nsXULPrototypeElement*
+nsXULPrototypeDocument::GetRootElement()
+{
+ return mRoot;
+}
+
+
+void
+nsXULPrototypeDocument::SetRootElement(nsXULPrototypeElement* aElement)
+{
+ mRoot = aElement;
+}
+
+nsresult
+nsXULPrototypeDocument::AddProcessingInstruction(nsXULPrototypePI* aPI)
+{
+ NS_PRECONDITION(aPI, "null ptr");
+ if (!mProcessingInstructions.AppendElement(aPI)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+const nsTArray<RefPtr<nsXULPrototypePI> >&
+nsXULPrototypeDocument::GetProcessingInstructions() const
+{
+ return mProcessingInstructions;
+}
+
+void
+nsXULPrototypeDocument::AddStyleSheetReference(nsIURI* aURI)
+{
+ NS_PRECONDITION(aURI, "null ptr");
+ if (!mStyleSheetReferences.AppendObject(aURI)) {
+ NS_WARNING("mStyleSheetReferences->AppendElement() failed."
+ "Stylesheet overlay dropped.");
+ }
+}
+
+const nsCOMArray<nsIURI>&
+nsXULPrototypeDocument::GetStyleSheetReferences() const
+{
+ return mStyleSheetReferences;
+}
+
+NS_IMETHODIMP
+nsXULPrototypeDocument::GetHeaderData(nsIAtom* aField, nsAString& aData) const
+{
+ // XXX Not implemented
+ aData.Truncate();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsXULPrototypeDocument::SetHeaderData(nsIAtom* aField, const nsAString& aData)
+{
+ // XXX Not implemented
+ return NS_OK;
+}
+
+
+
+nsIPrincipal*
+nsXULPrototypeDocument::DocumentPrincipal()
+{
+ NS_PRECONDITION(mNodeInfoManager, "missing nodeInfoManager");
+ return mNodeInfoManager->DocumentPrincipal();
+}
+
+void
+nsXULPrototypeDocument::SetDocumentPrincipal(nsIPrincipal* aPrincipal)
+{
+ mNodeInfoManager->SetDocumentPrincipal(aPrincipal);
+}
+
+void
+nsXULPrototypeDocument::MarkInCCGeneration(uint32_t aCCGeneration)
+{
+ mCCGeneration = aCCGeneration;
+}
+
+nsNodeInfoManager*
+nsXULPrototypeDocument::GetNodeInfoManager()
+{
+ return mNodeInfoManager;
+}
+
+
+nsresult
+nsXULPrototypeDocument::AwaitLoadDone(XULDocument* aDocument, bool* aResult)
+{
+ nsresult rv = NS_OK;
+
+ *aResult = mLoaded;
+
+ if (!mLoaded) {
+ rv = mPrototypeWaiters.AppendElement(aDocument)
+ ? NS_OK : NS_ERROR_OUT_OF_MEMORY; // addrefs
+ }
+
+ return rv;
+}
+
+
+nsresult
+nsXULPrototypeDocument::NotifyLoadDone()
+{
+ // Call back to each XUL document that raced to start the same
+ // prototype document load, lost the race, but hit the XUL
+ // prototype cache because the winner filled the cache with
+ // the not-yet-loaded prototype object.
+
+ nsresult rv = NS_OK;
+
+ mLoaded = true;
+
+ for (uint32_t i = mPrototypeWaiters.Length(); i > 0; ) {
+ --i;
+ // true means that OnPrototypeLoadDone will also
+ // call ResumeWalk().
+ rv = mPrototypeWaiters[i]->OnPrototypeLoadDone(true);
+ if (NS_FAILED(rv)) break;
+ }
+ mPrototypeWaiters.Clear();
+
+ return rv;
+}
+
+void
+nsXULPrototypeDocument::TraceProtos(JSTracer* aTrc, uint32_t aGCNumber)
+{
+ // Only trace the protos once per GC.
+ if (mGCNumber == aGCNumber) {
+ return;
+ }
+
+ mGCNumber = aGCNumber;
+ if (mRoot) {
+ mRoot->TraceAllScripts(aTrc);
+ }
+}
diff --git a/dom/xul/nsXULPrototypeDocument.h b/dom/xul/nsXULPrototypeDocument.h
new file mode 100644
index 0000000000..81772eab3b
--- /dev/null
+++ b/dom/xul/nsXULPrototypeDocument.h
@@ -0,0 +1,143 @@
+/* -*- 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 nsXULPrototypeDocument_h__
+#define nsXULPrototypeDocument_h__
+
+#include "js/TracingAPI.h"
+#include "mozilla/Attributes.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsISerializable.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsIAtom;
+class nsIPrincipal;
+class nsIURI;
+class nsNodeInfoManager;
+class nsXULPrototypeElement;
+class nsXULPrototypePI;
+
+namespace mozilla {
+namespace dom {
+class XULDocument;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * A "prototype" document that stores shared document information
+ * for the XUL cache.
+ * Among other things, stores the tree of nsXULPrototype*
+ * objects, from which the real DOM tree is built later in
+ * XULDocument::ResumeWalk.
+ */
+class nsXULPrototypeDocument final : public nsISerializable
+{
+public:
+ static nsresult
+ Create(nsIURI* aURI, nsXULPrototypeDocument** aResult);
+
+ // nsISupports interface
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ // nsISerializable interface
+ NS_DECL_NSISERIALIZABLE
+
+ nsresult InitPrincipal(nsIURI* aURI, nsIPrincipal* aPrincipal);
+ nsIURI* GetURI();
+
+ /**
+ * Get/set the root nsXULPrototypeElement of the document.
+ */
+ nsXULPrototypeElement* GetRootElement();
+ void SetRootElement(nsXULPrototypeElement* aElement);
+
+ /**
+ * Add a processing instruction to the prolog. Note that only
+ * PI nodes are currently stored in a XUL prototype document's
+ * prolog and that they're handled separately from the rest of
+ * prototype node tree.
+ *
+ * @param aPI an already adrefed PI proto to add. This method takes
+ * ownership of the passed PI.
+ */
+ nsresult AddProcessingInstruction(nsXULPrototypePI* aPI);
+ /**
+ * @note GetProcessingInstructions retains the ownership (the PI
+ * protos only get deleted when the proto document is deleted)
+ */
+ const nsTArray<RefPtr<nsXULPrototypePI> >& GetProcessingInstructions() const;
+
+ /**
+ * Access the array of style overlays for this document.
+ *
+ * Style overlays are stylesheets that need to be applied to the
+ * document, but are not referenced from within the document. They
+ * are currently obtained from the chrome registry via
+ * nsIXULOverlayProvider::getStyleOverlays.)
+ */
+ void AddStyleSheetReference(nsIURI* aStyleSheet);
+ const nsCOMArray<nsIURI>& GetStyleSheetReferences() const;
+
+ /**
+ * Access HTTP header data.
+ * @note Not implemented.
+ */
+ NS_IMETHOD GetHeaderData(nsIAtom* aField, nsAString& aData) const;
+ NS_IMETHOD SetHeaderData(nsIAtom* aField, const nsAString& aData);
+
+ nsIPrincipal *DocumentPrincipal();
+ void SetDocumentPrincipal(nsIPrincipal *aPrincipal);
+
+ /**
+ * If current prototype document has not yet finished loading,
+ * appends aDocument to the list of documents to notify (via
+ * XULDocument::OnPrototypeLoadDone()) and sets aLoaded to false.
+ * Otherwise sets aLoaded to true.
+ */
+ nsresult AwaitLoadDone(mozilla::dom::XULDocument* aDocument, bool* aResult);
+
+ /**
+ * Notifies each document registered via AwaitLoadDone on this
+ * prototype document that the prototype has finished loading.
+ * The notification is performed by calling
+ * nsIXULDocument::OnPrototypeLoadDone on the registered documents.
+ */
+ nsresult NotifyLoadDone();
+
+ nsNodeInfoManager *GetNodeInfoManager();
+
+ void MarkInCCGeneration(uint32_t aCCGeneration);
+
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsXULPrototypeDocument)
+
+ void TraceProtos(JSTracer* aTrc, uint32_t aGCNumber);
+
+protected:
+ nsCOMPtr<nsIURI> mURI;
+ RefPtr<nsXULPrototypeElement> mRoot;
+ nsTArray<RefPtr<nsXULPrototypePI> > mProcessingInstructions;
+ nsCOMArray<nsIURI> mStyleSheetReferences;
+
+ bool mLoaded;
+ nsTArray< RefPtr<mozilla::dom::XULDocument> > mPrototypeWaiters;
+
+ RefPtr<nsNodeInfoManager> mNodeInfoManager;
+
+ uint32_t mCCGeneration;
+ uint32_t mGCNumber;
+
+ nsXULPrototypeDocument();
+ virtual ~nsXULPrototypeDocument();
+ nsresult Init();
+
+ friend NS_IMETHODIMP
+ NS_NewXULPrototypeDocument(nsXULPrototypeDocument** aResult);
+
+ static uint32_t gRefCnt;
+};
+
+#endif // nsXULPrototypeDocument_h__
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 0000000000..a6eeb104be
--- /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 0000000000..fad5abfb60
--- /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 0000000000..8cc486e272
--- /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 0000000000..4bda7316a2
--- /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 0000000000..ea797c2cf1
--- /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 0000000000..e947960dab
--- /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 0000000000..9fb9560f67
--- /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 0000000000..e41af81e94
--- /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 0000000000..d8565643ac
--- /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 0000000000..ed99a3ae89
--- /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 0000000000..3beb0b7e5b
--- /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 0000000000..ec10fde74e
--- /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 0000000000..aef7de83e1
--- /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 0000000000..53253a3049
--- /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 0000000000..ebea5bcf61
--- /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 0000000000..33ae2b3e8b
--- /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 0000000000..c8eb6a8bb7
--- /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 0000000000..755b57e574
--- /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 0000000000..e064cf3b83
--- /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 0000000000..6a8ac24391
--- /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 0000000000..59c88e0725
--- /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 0000000000..9079d4189e
--- /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 0000000000..5b9c7b9f1c
--- /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 0000000000..120adfa79c
--- /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 0000000000..92c8b16ca2
--- /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 0000000000..a968097436
--- /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 0000000000..0ed96909e7
--- /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 0000000000..0bb96a5b5a
--- /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 0000000000..4db2f8983f
--- /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 0000000000..2fa08f2b87
--- /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 0000000000..673552a26b
--- /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 0000000000..b4eb706ed2
--- /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 0000000000..572cce4d35
--- /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 0000000000..0e450fadf5
--- /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 0000000000..64f4aad197
--- /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 0000000000..1f50538a82
--- /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 0000000000..c2cee5bbd0
--- /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 0000000000..2b44027227
--- /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 0000000000..cb828b0933
--- /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 0000000000..1ba1c9d87e
--- /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 0000000000..ded2fe1ab0
--- /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 0000000000..6d82740e3d
--- /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 0000000000..d7821ba29f
--- /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 0000000000..b77a972135
--- /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 0000000000..801af02269
--- /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 0000000000..9c1965ce2c
--- /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 0000000000..e728135989
--- /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 0000000000..71c285cc4c
--- /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 0000000000..5e128b42ed
--- /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 0000000000..c106fd91ec
--- /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 0000000000..eaea7e9963
--- /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 0000000000..ab3e134610
--- /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 0000000000..306481e0d4
--- /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 0000000000..49fb3335d7
--- /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 0000000000..7da8ffc983
--- /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 0000000000..732e545d05
--- /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 0000000000..30ac34d237
--- /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 0000000000..a70307bf53
--- /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 0000000000..8c52f139b6
--- /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 0000000000..1c6fed2524
--- /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 0000000000..dec5084158
--- /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 0000000000..9d53bab7d7
--- /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 0000000000..cb50224201
--- /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 0000000000..1ab9a79597
--- /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 0000000000..76aa28774a
--- /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 0000000000..b840fc2c75
--- /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 0000000000..75cf7b05a4
--- /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 0000000000..6ac4e60043
--- /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 0000000000..3c42c08290
--- /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 0000000000..b421334849
--- /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 0000000000..06fee7ac55
--- /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 0000000000..7ba88ff74b
--- /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 0000000000..f73ec718b1
--- /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 0000000000..4be55657f6
--- /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 0000000000..dca97ba78d
--- /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 0000000000..97989723cd
--- /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 0000000000..428f1045f6
--- /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 0000000000..85c49e52e8
--- /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 0000000000..75190c17b7
--- /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 0000000000..67cc819313
--- /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 0000000000..0f15cfa297
--- /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 0000000000..7be6df1989
--- /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 0000000000..cee01d41a2
--- /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 0000000000..af1de3f0ef
--- /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 0000000000..42e9a16330
--- /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 0000000000..2af4f89aae
--- /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 0000000000..d8b6110bef
--- /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 0000000000..a7bd190a78
--- /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 0000000000..514979d59c
--- /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 0000000000..7b90d64b16
--- /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 0000000000..f9100047f9
--- /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 0000000000..909a05e347
--- /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 0000000000..933c51bc05
--- /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 0000000000..f3b58502db
--- /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 0000000000..1bd31525e6
--- /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 0000000000..8e0e37c3a8
--- /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 0000000000..6895034f20
--- /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 0000000000..b2ef77d9ed
--- /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 0000000000..d38bd96c0f
--- /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 0000000000..d0e4faadff
--- /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 0000000000..2e9ce8a2f5
--- /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 0000000000..deee822dd4
--- /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 0000000000..12575c6b89
--- /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 0000000000..a9a14e00ab
--- /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 0000000000..b03f20ce23
--- /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 0000000000..e0ef6a7324
--- /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 0000000000..95c016fe8b
--- /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 0000000000..14d8bab385
--- /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 0000000000..2231f2067d
--- /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 0000000000..e754b15422
--- /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 0000000000..5e2ffe3269
--- /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 0000000000..c231b8670f
--- /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 0000000000..5127179340
--- /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 0000000000..eecb6a7edb
--- /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 0000000000..1597e31641
--- /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 0000000000..bebc7b9707
--- /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 0000000000..446fa77ec0
--- /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 0000000000..2c3a3e23c0
--- /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 0000000000..25b5d59a09
--- /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 0000000000..f03623e09e
--- /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 0000000000..bd14bee1f4
--- /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 0000000000..dae20a8308
--- /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 0000000000..afcb1d34ad
--- /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 0000000000..c5d94e0e28
--- /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 0000000000..c0114effe4
--- /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 0000000000..98006fc0d9
--- /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 0000000000..8604a1321f
--- /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 0000000000..9f3f362888
--- /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 0000000000..b5b3acf79e
--- /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 0000000000..b13ccdb7fb
--- /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 0000000000..633df88342
--- /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 0000000000..bd10d0d1f4
--- /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 0000000000..ed9c93843d
--- /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 0000000000..1f66279312
--- /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 0000000000..29b19f9846
--- /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 0000000000..713cb371ce
--- /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 0000000000..e1ac87c72c
--- /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 0000000000..4509c10187
--- /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 0000000000..68060c6b48
--- /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 0000000000..7abb67e1c5
--- /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 0000000000..3998f17961
--- /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 0000000000..df487a26fc
--- /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 0000000000..fdd1efb207
--- /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 0000000000..83887358fb
--- /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 0000000000..5d36585351
--- /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 0000000000..1cfb689b04
--- /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 0000000000..265a5bb144
--- /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 0000000000..bf67ebf9d3
--- /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 0000000000..2c9aedc7be
--- /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 0000000000..712cd4a449
--- /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 0000000000..e644d16ac2
--- /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 0000000000..18e8b72a9e
--- /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 0000000000..5582e48453
--- /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 0000000000..8715d3cc18
--- /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 0000000000..ba8591f608
--- /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 0000000000..a2a3eb55e3
--- /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 0000000000..e8e05acf2d
--- /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 0000000000..5aa5172c93
--- /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 0000000000..3fb1fdfa09
--- /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 0000000000..238540fb48
--- /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 0000000000..31aa9c4044
--- /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 0000000000..3ef3b1d3d4
--- /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 0000000000..73d7f742e8
--- /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 0000000000..fccee70ce4
--- /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 0000000000..2ca63da19e
--- /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 0000000000..abc28ff0fe
--- /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 0000000000..4d2404acab
--- /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 0000000000..0dd1a7ccf6
--- /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 0000000000..c31401dd43
--- /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 0000000000..6d2bc806b4
--- /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 0000000000..41df3d4d85
--- /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 0000000000..b8864c3b33
--- /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 0000000000..2d30ced27a
--- /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 0000000000..5f90a83346
--- /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 0000000000..612a50fc60
--- /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 0000000000..e3fb2e37b2
--- /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 0000000000..fdab290544
--- /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 0000000000..1979fdb98f
--- /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 0000000000..8176553e41
--- /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 0000000000..d328d81b23
--- /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 0000000000..653584c52a
--- /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 0000000000..8fcb569a78
--- /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 0000000000..346a988a59
--- /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 0000000000..7281eb6ef6
--- /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 0000000000..151c5d5ae5
--- /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 0000000000..893f056b81
--- /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 0000000000..1daf2dab9b
--- /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 0000000000..8d9137ffa8
--- /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 0000000000..6a396d3b9b
--- /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 0000000000..fd8200a66a
--- /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 0000000000..f2fb6ca9d4
--- /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 0000000000..d995c4c1cb
--- /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 0000000000..b11367e280
--- /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 0000000000..81517d2dfd
--- /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 0000000000..22be02c281
--- /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 0000000000..da9c83d0ce
--- /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 0000000000..39df0edac1
--- /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 0000000000..c791c6acda
--- /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 0000000000..43661c417f
--- /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 0000000000..28dcaa9265
--- /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 0000000000..0df5618849
--- /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 0000000000..0036827505
--- /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 0000000000..0ac6d3857d
--- /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 0000000000..376cee25ce
--- /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 0000000000..3ab75fcf48
--- /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 0000000000..600db67c4c
--- /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 0000000000..b99c849e76
--- /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 0000000000..9ca0427132
--- /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 0000000000..bfb494c793
--- /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 0000000000..9a07372fef
--- /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 0000000000..106d4d7cde
--- /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 0000000000..1002cd00c5
--- /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 0000000000..282beb69d2
--- /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 0000000000..8c9e5c012f
--- /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 0000000000..387279240a
--- /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 0000000000..e857c8e0ed
--- /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 0000000000..ff447916e2
--- /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 0000000000..7935061432
--- /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 0000000000..009dd13678
--- /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 0000000000..3cd7a8f07b
--- /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 0000000000..43c926ec87
--- /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 0000000000..da6c525075
--- /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 0000000000..a4f0e24172
--- /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 0000000000..dcc14e0bb4
--- /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 0000000000..fb94cbdadd
--- /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 0000000000..654e79e1c8
--- /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 0000000000..17528d34b9
--- /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 0000000000..1fda7ec75b
--- /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 0000000000..987c9a4683
--- /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 0000000000..4ae159991a
--- /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 0000000000..4f11a3de3a
--- /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 0000000000..130f3ee760
--- /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 0000000000..b6a6a1de33
--- /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 0000000000..9885dbfcea
--- /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 0000000000..40cd738544
--- /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 0000000000..09818b1e1c
--- /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 0000000000..ecec06997e
--- /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 0000000000..07a421ef00
--- /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 0000000000..b1934edca8
--- /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 0000000000..033c875063
--- /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 0000000000..45569d41e9
--- /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 0000000000..1519862fa9
--- /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 0000000000..69a6d68fca
--- /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 0000000000..34794c51a0
--- /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 0000000000..8d3d0c5750
--- /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 0000000000..2679c607e1
--- /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 0000000000..e26be898b3
--- /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 0000000000..00558388d2
--- /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 0000000000..a66792dd76
--- /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 0000000000..67b3f2fcf6
--- /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 0000000000..edcac5542e
--- /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 0000000000..cd877f1ce0
--- /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 0000000000..16eca331e1
--- /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 0000000000..c036a3c6ab
--- /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 0000000000..a6ae121353
--- /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 0000000000..d2779a20f3
--- /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 0000000000..455fd147f0
--- /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 0000000000..b67200b57e
--- /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 0000000000..5276fb598e
--- /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 0000000000..7daa0a7ccc
--- /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 0000000000..22f3515472
--- /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 0000000000..f7c1e4b11b
--- /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 0000000000..4c42f54e2f
--- /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 0000000000..46e9d78196
--- /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 0000000000..728e7ae3da
--- /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 0000000000..445f5efa13
--- /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 0000000000..b7e79ba18e
--- /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 0000000000..c6b566fbe1
--- /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 0000000000..53507b0a34
--- /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 0000000000..6e86c981d4
--- /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 0000000000..434ba40c40
--- /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 0000000000..4876ed3839
--- /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 0000000000..4ab12c74d7
--- /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 0000000000..9fd794b92e
--- /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 0000000000..41e2be959d
--- /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 0000000000..14c03898c4
--- /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 0000000000..dd93aa2855
--- /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 0000000000..9a576e3702
--- /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 0000000000..39992b43e6
--- /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 0000000000..fdbbed3217
--- /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 0000000000..8f7e747c3a
--- /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 0000000000..90372f62c6
--- /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 0000000000..57ca2d838b
--- /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 0000000000..160d4bddbb
--- /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 0000000000..010a6ba49a
--- /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 0000000000..d9640fbde1
--- /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 0000000000..075d2c4535
--- /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 0000000000..ce728b2b12
--- /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 0000000000..b83fc3595a
--- /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 0000000000..ad1829d0d5
--- /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>
diff --git a/dom/xul/test/1061864.html b/dom/xul/test/1061864.html
new file mode 100644
index 0000000000..ec8091a6bd
--- /dev/null
+++ b/dom/xul/test/1061864.html
@@ -0,0 +1,8 @@
+<html>
+<body>
+<script>
+</script>
+<iframe id="childiframe" src="data:text/html,Test">
+</iframe>
+</body>
+</html>
diff --git a/dom/xul/test/398289-resource.xul b/dom/xul/test/398289-resource.xul
new file mode 100644
index 0000000000..71728e58b5
--- /dev/null
+++ b/dom/xul/test/398289-resource.xul
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ buttonlabelaccept="OK"
+ ondialogaccept="alert('OK');"
+ buttonlabelcancel="Cancel"
+ ondialogcancel="alert('Cancel');">
+
+ <tabbox id="test" flex="1" persist="selectedIndex">
+
+ <tabs>
+ <tab label="One"/>
+ <tab label="Two"/>
+ </tabs>
+
+ <tabpanels flex="1">
+
+ <vbox flex="1">
+ <description>Text for tab ONE</description>
+ <description class="text-link"
+ onclick="window.open('https://bugzilla.mozilla.org/show_bug.cgi?id=398289');">(test case for bug 398289)</description>
+ <tree>
+ <treecols>
+ <treecol label="Header" flex="1"/>
+ </treecols>
+ </tree>
+ </vbox>
+
+ <vbox flex="1">
+ <description>Text for tab TWO</description>
+ <description>(When the document is reloaded, this content gets replaced by the one from the first tab.)</description>
+ </vbox>
+
+ </tabpanels>
+
+ </tabbox>
+
+ <box height="1000"/> <!-- Push dialog buttons out of sight so that the animated default button isn't part of the snapshot -->
+
+</dialog>
diff --git a/dom/xul/test/bug497875-iframe.xul b/dom/xul/test/bug497875-iframe.xul
new file mode 100644
index 0000000000..8365e7df0f
--- /dev/null
+++ b/dom/xul/test/bug497875-iframe.xul
@@ -0,0 +1,6 @@
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+<box onerror="document.loadOverlay('file:///does-not-exist', null);" >
+<html:script src="ftp://some.website.that.will.cause.an.error"/>
+</box>
+</window>
diff --git a/dom/xul/test/chrome.ini b/dom/xul/test/chrome.ini
new file mode 100644
index 0000000000..d65bf168ff
--- /dev/null
+++ b/dom/xul/test/chrome.ini
@@ -0,0 +1,40 @@
+[DEFAULT]
+support-files =
+ 398289-resource.xul
+ bug497875-iframe.xul
+ file_bug236853.rdf
+ overlay1_bug335375.xul
+ overlay2_bug335375.xul
+ window_bug583948.xul
+ window_bug757137.xul
+ 1061864.html
+ file_bug1271240.xul
+ file_bug1069772.xul
+
+[test_bug199692.xul]
+[test_bug233643.xul]
+[test_bug236853.xul]
+[test_bug311681.xul]
+[test_bug335375.xul]
+[test_bug391002.xul]
+[test_bug398289.html]
+[test_bug403868.xul]
+[test_bug414907.xul]
+[test_bug418216.xul]
+[test_bug445177.xul]
+[test_bug449457.xul]
+[test_bug468176.xul]
+[test_bug497875.xul]
+[test_bug583948.xul]
+[test_bug640158_overlay_persist.xul]
+[test_bug757137.xul]
+[test_bug775972.xul]
+[test_bug1061864_1.xul]
+[test_bug1061864_2.xul]
+[test_bug1070049_throw_from_script.xul]
+[test_import_xul_to_content.xul]
+[test_bug1271240.xul]
+skip-if = os == "android"
+[test_bug1069772.xul]
+skip-if = os == "android"
+[test_bug1290965.xul]
diff --git a/dom/xul/test/file_bug1069772.xul b/dom/xul/test/file_bug1069772.xul
new file mode 100644
index 0000000000..b909852469
--- /dev/null
+++ b/dom/xul/test/file_bug1069772.xul
@@ -0,0 +1,143 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1069772
+-->
+<window title="Mozilla Bug 1069772"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="run();">
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components;
+ Cu.import("resource://testing-common/ContentTask.jsm");
+ Cu.import("resource://testing-common/BrowserTestUtils.jsm");
+ Cu.import("resource://gre/modules/Services.jsm");
+ ContentTask.setTestScope(window.opener.wrappedJSObject);
+
+ let imports = ['SimpleTest', 'ok', 'is'];
+ for (let name of imports) {
+ window[name] = window.opener.wrappedJSObject[name];
+ }
+
+ /** Test for Bug 1069772 **/
+ function run() {
+ // test the transition:
+ // prerender => visible => hidden => visible
+ // on a non-remote browser
+ createPrerenderedBrowser(false)
+ .then(browser => verifyBrowser(browser, true, false))
+ .then(browser => verifyVisibility(browser, 'prerender'))
+ .then(browser => makePrerenderedBrowserActive(browser))
+ .then(browser => verifyBrowser(browser, false, false))
+ .then(browser => verifyVisibility(browser, 'visible'))
+ .then(browser => hideBrowser(browser))
+ .then(browser => verifyBrowser(browser, false, false))
+ .then(browser => verifyVisibility(browser, 'hidden'))
+ .then(browser => showBrowser(browser))
+ .then(browser => verifyBrowser(browser, false, false))
+ .then(browser => verifyVisibility(browser, 'visible'))
+
+ // test the transition:
+ // prerender => visible => hidden => visible
+ // on a remote browser
+ .then(() => createPrerenderedBrowser(true))
+ .then(browser => verifyBrowser(browser, true, true))
+ .then(browser => verifyVisibility(browser, 'prerender'))
+ .then(browser => makePrerenderedBrowserActive(browser))
+ .then(browser => verifyBrowser(browser, false, true))
+ .then(browser => verifyVisibility(browser, 'visible'))
+ .then(browser => hideBrowser(browser))
+ .then(browser => verifyBrowser(browser, false, true))
+ .then(browser => verifyVisibility(browser, 'hidden'))
+ .then(browser => showBrowser(browser))
+ .then(browser => verifyBrowser(browser, false, true))
+ .then(browser => verifyVisibility(browser, 'visible'))
+
+ // finish test
+ .then(() => {
+ window.close();
+ SimpleTest.finish();
+ });
+ }
+
+ function createPrerenderedBrowser(remote) {
+ let browser = document.createElement('browser');
+ browser.setAttribute('type', 'content');
+ browser.setAttribute('prerendered', true);
+ browser.setAttribute('remote', remote);
+ browser.setAttribute('src', 'data:text/html;charset=UTF-8,<html><body>' +
+ '<iframe id="iframe" src="data:text/html;charset=UTF-8,Hello Frame!">' +
+ '</iframe></body></html>');
+
+ // wait for 'load' and 'pageshow'
+ let promises = [];
+ promises.push(BrowserTestUtils.browserLoaded(browser));
+ promises.push(new Promise(resolve =>
+ Services.mm.addMessageListener('test:pageshow', resolve)));
+ Services.mm.loadFrameScript('data:,' +
+ 'addEventListener("pageshow", ' +
+ '() => sendAsyncMessage("test:pageshow", null), false);',
+ true);
+
+ document.getElementById('stack').appendChild(browser);
+ return Promise.all(promises).then(() => browser);
+ }
+
+ function verifyBrowser(browser, prerendered, remote) {
+ let docShellOrTabParent = remote ?
+ browser.frameLoader.tabParent : browser.frameLoader.docShell;
+ ok(docShellOrTabParent, 'docShellOrTabParent should not be null');
+ is(docShellOrTabParent.isPrerendered, prerendered,
+ 'isPrerendered should be ' + prerendered);
+ return browser;
+ }
+
+ function verifyVisibility(browser, visibility) {
+ return ContentTask.spawn(browser, visibility, (v) => {
+ let iframe = content.document.getElementById('iframe');
+ is(content.document.visibilityState, v, 'check doc.visibilityState');
+ is(content.document.hidden, v != 'visible', 'check doc.hidden');
+ is(iframe.contentDocument.visibilityState, v, 'check iframe doc.visibilityState');
+ is(iframe.contentDocument.hidden, v != 'visible', 'check iframe doc.hidden');
+ }).then(() => browser);
+ }
+
+ function makePrerenderedBrowserActive(browser) {
+ let promise = waitForVisibilityChange(browser);
+ browser.setAttribute('prerendered', false);
+ browser.makePrerenderedBrowserActive();
+ return promise.then(() => browser);
+ }
+
+ function hideBrowser(browser) {
+ let promise = waitForVisibilityChange(browser);
+ browser.docShellIsActive = false;
+ return promise.then(() => browser);
+ }
+
+ function showBrowser(browser) {
+ let promise = waitForVisibilityChange(browser);
+ browser.docShellIsActive = true;
+ return promise.then(() => browser);
+ }
+
+ function waitForVisibilityChange(browser) {
+ return ContentTask.spawn(browser, null, () => {
+ return new Promise(resolve => {
+ let iframe = content.document.getElementById('iframe');
+ iframe.contentDocument.addEventListener('visibilitychange', function listener() {
+ iframe.contentDocument.removeEventListener('visibilitychange', listener);
+ resolve();
+ });
+ });
+ });
+ }
+
+ ]]>
+ </script>
+ <stack id="stack" flex="1" />
+</window>
diff --git a/dom/xul/test/file_bug1271240.xul b/dom/xul/test/file_bug1271240.xul
new file mode 100644
index 0000000000..2edd5a1b40
--- /dev/null
+++ b/dom/xul/test/file_bug1271240.xul
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1271240
+-->
+<window title="Mozilla Bug 1271240"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="run();">
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ function ok(cond, msg) {
+ opener.wrappedJSObject.ok(cond, msg);
+ }
+
+ function is(actual, expected, msg) {
+ opener.wrappedJSObject.is(actual, expected, msg);
+ }
+
+ /** Test for Bug 1271240 **/
+ function run() {
+ // test non-remote, non-prerendered browser
+ createBrowser(false, false)
+ .then(browser => verifyBrowser(browser, false, false))
+
+ // test non-remote, prerendered browser
+ .then(() => createBrowser(true, false))
+ .then(browser => verifyBrowser(browser, true, false))
+ .then(browser => makePrerenderedBrowserActive(browser))
+ .then(browser => verifyBrowser(browser, false, false))
+
+ // test remote, non-prerendered browser
+ .then(() => createBrowser(false, true))
+ .then(browser => verifyBrowser(browser, false, true))
+
+ // test remote, prerendered browser
+ .then(() => createBrowser(true, true))
+ .then(browser => verifyBrowser(browser, true, true))
+ .then(browser => makePrerenderedBrowserActive(browser))
+ .then(browser => verifyBrowser(browser, false, true))
+
+ // finish test
+ .then(() => {
+ opener.postMessage('finish', '*');
+ window.close();
+ });
+ }
+
+ function createBrowser(prerendered, remote) {
+ return new Promise(resolve => {
+ var browser = document.createElement('browser');
+ browser.setAttribute('type', 'content');
+ browser.setAttribute('prerendered', prerendered);
+ browser.setAttribute('remote', remote);
+ document.documentElement.appendChild(browser);
+ browser.loadURI('example.com');
+ resolve(browser);
+ });
+ }
+
+ function verifyBrowser(browser, prerendered, remote) {
+ var docShellOrTabParent = remote ?
+ browser.frameLoader.tabParent : browser.frameLoader.docShell;
+ ok(docShellOrTabParent, 'docShellOrTabParent should not be null');
+ is(docShellOrTabParent.isPrerendered, prerendered,
+ 'isPrerendered should be ' + prerendered);
+ return browser;
+ }
+
+ function makePrerenderedBrowserActive(browser) {
+ browser.makePrerenderedBrowserActive();
+ return browser;
+ }
+
+ ]]>
+ </script>
+ <!-- <browser type="content-primary" flex="1" id="content" />
+ <browser type="content-primary" flex="1" id="content-remote" remote="true" /> -->
+</window>
diff --git a/dom/xul/test/file_bug236853.rdf b/dom/xul/test/file_bug236853.rdf
new file mode 100644
index 0000000000..8d17bf6912
--- /dev/null
+++ b/dom/xul/test/file_bug236853.rdf
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:ex="http://www.ex.org/ex-rdf#">
+
+ <rdf:Description about="urn:root">
+ <ex:nodes>
+ <rdf:Bag about="http://www.ex.org/nodes">
+ <rdf:li>
+ <rdf:Description about="http://www.ex.org/nodes/A"/>
+ </rdf:li>
+ </rdf:Bag>
+ </ex:nodes>
+ </rdf:Description>
+</rdf:RDF>
diff --git a/dom/xul/test/mochitest.ini b/dom/xul/test/mochitest.ini
new file mode 100644
index 0000000000..4ef4e2b996
--- /dev/null
+++ b/dom/xul/test/mochitest.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+
+[test_bug486990.xul]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_bug749367.xul]
diff --git a/dom/xul/test/overlay1_bug335375.xul b/dom/xul/test/overlay1_bug335375.xul
new file mode 100644
index 0000000000..a3448b7f87
--- /dev/null
+++ b/dom/xul/test/overlay1_bug335375.xul
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<overlay id="overlay1"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <commandset id="test_commandset">
+ <command id="cmd_test"/>
+ </commandset>
+ <toolbarbutton id="button_test"
+ command="cmd_test"/>
+</overlay>
diff --git a/dom/xul/test/overlay2_bug335375.xul b/dom/xul/test/overlay2_bug335375.xul
new file mode 100644
index 0000000000..cb37db6420
--- /dev/null
+++ b/dom/xul/test/overlay2_bug335375.xul
@@ -0,0 +1,5 @@
+<?xml version="1.0"?>
+<overlay id="overlay2"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <command id="cmd_test" accesskey="C"/>
+</overlay>
diff --git a/dom/xul/test/overlay_640158.xul b/dom/xul/test/overlay_640158.xul
new file mode 100644
index 0000000000..536beb5291
--- /dev/null
+++ b/dom/xul/test/overlay_640158.xul
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<overlay id="overlay1"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <window id="rootwin">
+ <box id="bar" testattr="original"/>
+ </window>
+</overlay>
+
diff --git a/dom/xul/test/test_bug1061864_1.xul b/dom/xul/test/test_bug1061864_1.xul
new file mode 100644
index 0000000000..8fe3b8e301
--- /dev/null
+++ b/dom/xul/test/test_bug1061864_1.xul
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1061864
+-->
+<window title="Mozilla Bug 1061864"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="RunTest();">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 1061864 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function RunTest()
+ {
+ // Test that the docshell belonging to a prerendered frame loader will
+ // be created with the correct prerendered flag.
+ test(false, function() {
+ test(true, function() {
+ SimpleTest.finish();
+ });
+ });
+ }
+
+ function test(prerendered, callback) {
+ var parentIframe = document.createElement("iframe");
+ if (prerendered) {
+ parentIframe.setIsPrerendered();
+ }
+ parentIframe.onload = function() {
+ var docShell = parentIframe.frameLoader.docShell;
+ is(docShell.isPrerendered, prerendered, "The docshell is" + (prerendered ? "" : " not") + " prerendered");
+ callback();
+ }
+ document.documentElement.appendChild(parentIframe);
+ }
+ ]]>
+ </script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1061864"
+ target="_blank">Mozilla Bug 1061864</a>
+ </body>
+</window>
diff --git a/dom/xul/test/test_bug1061864_2.xul b/dom/xul/test/test_bug1061864_2.xul
new file mode 100644
index 0000000000..e7ed5a630c
--- /dev/null
+++ b/dom/xul/test/test_bug1061864_2.xul
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1061864
+-->
+<window title="Mozilla Bug 1061864"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="RunTest();">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 1061864 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function RunTest()
+ {
+ // Test that the docshell prerendered flag will be correctly inherited in
+ // prerendered documents.
+ test(false, function() {
+ test(true, function() {
+ SimpleTest.finish();
+ });
+ });
+ }
+
+ function test(prerendered, callback) {
+ var parentIframe = document.createElement("iframe");
+ if (prerendered) {
+ parentIframe.setIsPrerendered();
+ }
+ parentIframe.setAttribute("src", "1061864.html");
+ parentIframe.onload = function() {
+ var childIframe = parentIframe.contentDocument.getElementById("childiframe");
+ var childDocShell = childIframe.frameLoader.docShell;
+ is(childDocShell.isPrerendered, prerendered, "The docshell is" + (prerendered ? "" : " not") + " prerendered");
+ callback();
+ }
+ document.documentElement.appendChild(parentIframe);
+ }
+ ]]>
+ </script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1061864"
+ target="_blank">Mozilla Bug 1061864</a>
+ </body>
+</window>
diff --git a/dom/xul/test/test_bug1069772.xul b/dom/xul/test/test_bug1069772.xul
new file mode 100644
index 0000000000..173113fdb6
--- /dev/null
+++ b/dom/xul/test/test_bug1069772.xul
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1069772
+-->
+<window title="Mozilla Bug 1069772"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="loadTest();">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069772"
+ target="_blank">Mozilla Bug 1069772</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 1069772 **/
+ SimpleTest.waitForExplicitFinish();
+ function loadTest() {
+ window.open("file_bug1069772.xul", "", "width=360,height=240,chrome");
+ }
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/xul/test/test_bug1070049_throw_from_script.xul b/dom/xul/test/test_bug1070049_throw_from_script.xul
new file mode 100644
index 0000000000..baa04a89ea
--- /dev/null
+++ b/dom/xul/test/test_bug1070049_throw_from_script.xul
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1070049
+-->
+<window title="Mozilla Bug 1070049"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 1070049 **/
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function() {
+ // Prevent the test from failing when the exception hits onerror.
+ SimpleTest.expectUncaughtException();
+
+ // Tell the test to expect exactly one console error with the given parameters,
+ // with SimpleTest.finish as a continuation function.
+ SimpleTest.monitorConsole(SimpleTest.finish, [{errorMessage: new RegExp('flimfniffle')}]);
+
+ // Schedule the console accounting (and continuation) to run next, right
+ // after we throw (below).
+ SimpleTest.executeSoon(SimpleTest.endMonitorConsole);
+
+ // Throw.
+ throw "flimfniffle";
+ });
+ ]]>
+ </script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1070049"
+ target="_blank">Mozilla Bug 1070049</a>
+ </body>
+</window>
diff --git a/dom/xul/test/test_bug1271240.xul b/dom/xul/test/test_bug1271240.xul
new file mode 100644
index 0000000000..cfba375239
--- /dev/null
+++ b/dom/xul/test/test_bug1271240.xul
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1271240
+-->
+<window title="Mozilla Bug 1271240"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="loadTest();">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1271240"
+ target="_blank">Mozilla Bug 1271240</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 1271240 **/
+ SimpleTest.waitForExplicitFinish();
+ window.addEventListener('message', (event) => {
+ if (event.data == 'finish') {
+ SimpleTest.finish();
+ }
+ });
+
+ function loadTest() {
+ window.open("file_bug1271240.xul", "", "chrome");
+ }
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/xul/test/test_bug1290965.xul b/dom/xul/test/test_bug1290965.xul
new file mode 100644
index 0000000000..8701b55be5
--- /dev/null
+++ b/dom/xul/test/test_bug1290965.xul
@@ -0,0 +1,39 @@
+<?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" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:h="http://www.w3.org/1999/xhtml">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <toolbarbutton oncommand="++countera;" id="a">A</toolbarbutton>
+ <toolbarbutton oncommand="++counterb;" id="b">B</toolbarbutton>
+ <script type="text/javascript">
+ <![CDATA[
+ let aEl = document.getElementById('a');
+ let bEl = document.getElementById('b');
+ let countera = 0;
+ let counterb = 0;
+
+ aEl.addEventListener('click', function (aEvent) {
+ aEvent.preventDefault();
+ let cmdEvent = document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent("command", true, true, window, 0,
+ aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey,
+ aEvent.metaKey, null);
+ aEvent.currentTarget.dispatchEvent(cmdEvent);
+ });
+
+ bEl.addEventListener('click', function (aEvent) {
+ let cmdEvent = document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent("command", true, true, window, 0,
+ aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey,
+ aEvent.metaKey, null);
+ aEvent.currentTarget.dispatchEvent(cmdEvent);
+ });
+
+ bEl.click();
+ aEl.click();
+
+ is(countera, 1, "Counter should be one as event fires once");
+ is(counterb, 2, "Counter should be two as event fires twice");
+ ]]>
+ </script>
+</window>
diff --git a/dom/xul/test/test_bug199692.xul b/dom/xul/test/test_bug199692.xul
new file mode 100644
index 0000000000..69a400a2a9
--- /dev/null
+++ b/dom/xul/test/test_bug199692.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"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=199692
+-->
+<window title="Test for Bug 199692"
+ id="test_bug199692_xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <vbox hidden="true">
+ <bindings xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <binding id="anon">
+ <content>
+ <xul:label id="anon-label" value="ANON"/>
+ </content>
+ </binding>
+ </bindings>
+ </vbox>
+
+<body id="body" xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=199692">Mozilla Bug 199692</a>
+
+<vbox id="content" style="position: relative;"
+xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <xul:label id="non-anon-label" value="a textbox!:" control="textbox"/>
+ <xul:textbox id="textbox" multiline="true" rows="4" />
+ <xul:radiogroup style="outline: 2px solid orange;">
+ <xul:radio id="unselected-radio" label="Orange" style="outline: 2px solid red;"/>
+ <xul:radio id="selected-radio" label="Violet" selected="true"/>
+ <xul:radio id="disabled-radio" label="Yellow" disabled="true"/>
+ </xul:radiogroup>
+ <hbox id="bound" style="-moz-binding:url('#anon'); border: 2px solid green;"></hbox>
+</vbox>
+<pre id="test">
+ <script class="testbody" type="text/javascript">
+<![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ // Before onload, XUL docs have no root frame.
+ is(document.elementFromPoint(10,10), null,
+ "Calls to elementFromPoint before onload should return null");
+
+ var d = 10;
+ function middle(e) {
+ return { "x": e.boxObject.x + e.boxObject.width/2,
+ "y": e.boxObject.y + e.boxObject.height/2 };
+ }
+ function lower_right(e) {
+ return { "x": e.boxObject.x + e.boxObject.width - d,
+ "y": e.boxObject.y + e.boxObject.height - d };
+ }
+ function upper_left(e) {
+ return { "x": e.boxObject.x + d,
+ "y": e.boxObject.y + d };
+ }
+ function scrollbar_button(e) { // a bit down from upper right
+ return { "x": e.boxObject.x + e.boxObject.width - d,
+ "y": e.boxObject.y + d + 15 };
+ }
+
+ function test(ptFunc, id, message) {
+ var pt = ptFunc($(id));
+ var e = document.elementFromPoint(pt.x, pt.y);
+ ok(e != null, message + " (returned null)");
+ is(e.id, id, message);
+ }
+
+ function do_test() {
+ // Avoid hardcoding x,y pixel values, to better deal with differing default
+ // font sizes or other layout defaults.
+
+ test(middle, 'textbox', "Point within textbox should return textbox element");
+ test(lower_right, 'textbox', "Point on textbox's scrollbar should return textbox element");
+ test(scrollbar_button, 'textbox', "Point on textbox's scrollbar button should return textbox element");
+ test(middle, 'non-anon-label', "Point on label should return label");
+ test(upper_left, 'bound', "Point on XBL content should return element with -moz-binding style");
+
+ SimpleTest.finish();
+ }
+ $("textbox").setAttribute("value",
+ "lorem ipsum dolor sit amet " +
+ "lorem ipsum dolor sit amet " +
+ "lorem ipsum dolor sit amet " +
+ "lorem ipsum dolor sit amet " +
+ "lorem ipsum dolor sit amet " +
+ "lorem ipsum dolor sit amet " +
+ "lorem ipsum dolor sit amet "); // force scrollbars to appear
+ addLoadEvent(do_test);
+]]>
+ </script>
+</pre>
+</body>
+</window>
diff --git a/dom/xul/test/test_bug233643.xul b/dom/xul/test/test_bug233643.xul
new file mode 100644
index 0000000000..a1cf22bc81
--- /dev/null
+++ b/dom/xul/test/test_bug233643.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"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Test for Bug 233643">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=233643
+-->
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- NOTE: This testcase depends on the patch for bug 366770
+ (ability to apply bindings with data: URIs). -->
+
+ <!-- The data URI: below corresponds to:
+ <?xml version="1.0"?>
+ <bindings xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <binding id="label">
+ <content>
+ <xul:label anonid="foo" style="display: none; color: green;" value="PASS"/>
+ </content>
+ <implementation>
+ <property name="fooDisplay">
+ <setter>document.getAnonymousElementByAttribute(this, "anonid", "foo").style.display = val;</setter>
+ <getter>return document.getAnonymousElementByAttribute(this, "anonid", "foo").style.display;</getter>
+ </property>
+ </implementation>
+ </binding>
+ </bindings>
+ -->
+ <html:style type="text/css">
+ foo {
+ -moz-binding: url(data:text/xml,%3C%3Fxml%20version%3D%221.0%22%3F%3E%0A%3Cbindings%20xmlns%3D%22http%3A//www.mozilla.org/xbl%22%0A%20%20%20%20%20%20%20%20%20%20xmlns%3Axul%3D%22http%3A//www.mozilla.org/keymaster/gatekeeper/there.is.only.xul%22%3E%0A%20%20%3Cbinding%20id%3D%22label%22%3E%0A%20%20%20%20%3Ccontent%3E%0A%20%20%20%20%20%20%3Cxul%3Alabel%20anonid%3D%22foo%22%20style%3D%22display%3A%20none%3B%20color%3A%20green%3B%22%20value%3D%22PASS%22/%3E%0A%20%20%20%20%3C/content%3E%0A%20%20%20%20%3Cimplementation%3E%0A%20%20%20%20%20%20%3Cproperty%20name%3D%22fooDisplay%22%3E%0A%20%20%20%20%20%20%20%20%3Csetter%3Edocument.getAnonymousElementByAttribute%28this%2C%20%22anonid%22%2C%20%22foo%22%29.style.display%20%3D%20val%3B%3C/setter%3E%0A%20%20%20%20%20%20%20%20%3Cgetter%3Ereturn%20document.getAnonymousElementByAttribute%28this%2C%20%22anonid%22%2C%20%22foo%22%29.style.display%3B%3C/getter%3E%0A%20%20%20%20%20%20%3C/property%3E%0A%20%20%20%20%3C/implementation%3E%0A%20%20%3C/binding%3E%0A%3C/bindings%3E);
+ }
+ </html:style>
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=233643">Mozilla Bug 233643</a>
+ <p id="display">
+ <foo xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
+ <foo xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
+ </p>
+ <div id="content" style="display: none">
+ <script class="testbody" type="text/javascript">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ function test_233643() {
+ var foos = document.getElementsByTagName("foo");
+ ok(foos.length == 2, "Found 2 <foo> elements");
+
+ // Make sure that the binding was applied successfully
+ ok(foos[0].fooDisplay !== undefined, "Binding applied");
+
+ // Show both both child elements by setting their display to "".
+ foos[0].fooDisplay = "";
+ foos[1].fooDisplay = "";
+
+ // Hide the second one. In a build with bug 233643, this hides both.
+ foos[1].fooDisplay = "none";
+
+ // Check that changing the second didn't affect the first.
+ ok(foos[0].fooDisplay != foos[1].fooDisplay, "XUL Element have their own style rules");
+
+ SimpleTest.finish();
+ }
+
+ function do_test() {
+ setTimeout(test_233643, 0);
+ }
+ addLoadEvent(do_test);
+ ]]>
+ </script>
+ </div>
+ </body>
+</window>
diff --git a/dom/xul/test/test_bug236853.xul b/dom/xul/test/test_bug236853.xul
new file mode 100644
index 0000000000..9b79deb758
--- /dev/null
+++ b/dom/xul/test/test_bug236853.xul
@@ -0,0 +1,37 @@
+<?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 type="application/javascript">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function() {
+ ok(true, "Didn't crash");
+ SimpleTest.finish();
+ }
+ ]]>
+ </script>
+ <vbox flex="1">
+ <svg:svg datasources="file_bug236853.rdf" ref="urn:root">
+ <template>
+ <rule>
+ <conditions>
+ <content uri="?root"/>
+ <triple subject="?root"
+ predicate="http://www.ex.org/ex-rdf#nodes"
+ object="?nodes"/>
+ <member container="?nodes" child="?node"/>
+ </conditions>
+ <action>
+ <!-- The line below causes Mozilla to crash -->
+ <svg:text uri="?node" x="64" y="64">Text</svg:text>
+ </action>
+ </rule>
+ </template>
+ <!--<svg:text x="64" y="64">Text</svg:text>-->
+ </svg:svg>
+ </vbox>
+</window>
diff --git a/dom/xul/test/test_bug311681.xul b/dom/xul/test/test_bug311681.xul
new file mode 100644
index 0000000000..2774d87313
--- /dev/null
+++ b/dom/xul/test/test_bug311681.xul
@@ -0,0 +1,106 @@
+<?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=311681
+-->
+<window title="Mozilla Bug 311681"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ 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=311681">Mozilla Bug 311681</a>
+<script class="testbody" type="text/javascript">
+<![CDATA[
+ // Setup script
+ SimpleTest.waitForExplicitFinish();
+
+ // Make sure to trigger the hashtable case by asking for enough elements
+ // by ID.
+ for (var i = 0; i < 256; ++i) {
+ var x = document.getElementById(i);
+ }
+
+ // save off the document.getElementById function, since getting it as a
+ // property off the document it causes a content flush.
+ var fun = document.getElementById;
+
+ // Slot for our initial element with id "content"
+ var testNode;
+
+ function getCont() {
+ return fun.call(document, "content");
+ }
+
+ function testClone() {
+ // Test to make sure that if we have multiple nodes with the same ID in
+ // a document we don't forget about one of them when the other is
+ // removed.
+ var newParent = $("display");
+ var node = testNode.cloneNode(true);
+ isnot(node, testNode, "Clone should be a different node");
+
+ newParent.appendChild(node);
+
+ // Check what getElementById returns, no flushing
+ is(getCont(), node, "Should be getting new node pre-flush 1")
+
+ // Trigger a layout flush, just in case.
+ var itemHeight = newParent.offsetHeight/10;
+
+ // Check what getElementById returns now.
+ is(getCont(), node, "Should be getting new node post-flush 1")
+
+ clear(newParent);
+
+ // Check what getElementById returns, no flushing
+ is(getCont(), testNode, "Should be getting orig node pre-flush 2");
+
+ // Trigger a layout flush, just in case.
+ var itemHeight = newParent.offsetHeight/10;
+
+ // Check what getElementById returns now.
+ is(getCont(), testNode, "Should be getting orig node post-flush 2");
+
+ node = testNode.cloneNode(true);
+ newParent.appendChild(node);
+ testNode.parentNode.removeChild(testNode);
+
+ // Check what getElementById returns, no flushing
+ is(getCont(), node, "Should be getting clone pre-flush");
+
+ // Trigger a layout flush, just in case.
+ var itemHeight = newParent.offsetHeight/10;
+
+ // Check what getElementById returns now.
+ is(getCont(), node, "Should be getting clone post-flush");
+
+ }
+
+ function clear(node) {
+ while (node.hasChildNodes()) {
+ node.removeChild(node.firstChild);
+ }
+ }
+
+ addLoadEvent(testClone);
+ addLoadEvent(SimpleTest.finish);
+]]>
+</script>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <script class="testbody" type="text/javascript">
+ <![CDATA[
+ testNode = fun.call(document, "content");
+ // Needs incremental XML parser
+ isnot(testNode, null, "Should have node here");
+ ]]>
+ </script>
+</div>
+<pre id="test">
+</pre>
+</body>
+
+</window>
diff --git a/dom/xul/test/test_bug335375.xul b/dom/xul/test/test_bug335375.xul
new file mode 100644
index 0000000000..20b8dd1d28
--- /dev/null
+++ b/dom/xul/test/test_bug335375.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"?>
+<?xul-overlay href="overlay1_bug335375.xul"?>
+<?xul-overlay href="overlay2_bug335375.xul"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=335375
+-->
+<window title="Mozilla Bug 335375"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="RunTest();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ function RunTest()
+ {
+ var cmd = document.getElementById("cmd_test");
+ var button = document.getElementById("button_test");
+
+ is(cmd.getAttribute("accesskey"), "C",
+ "checking command has an accesskey");
+ is(button.getAttribute("accesskey"), cmd.getAttribute("accesskey"),
+ "checking command and button have the same accesskey");
+
+ cmd.setAttribute("accesskey", "D");
+ is(button.getAttribute("accesskey"), "D",
+ "checking button has inherited new accesskey from command");
+
+ SimpleTest.finish();
+ }
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=335375"
+ target="_blank">Mozilla Bug 335375</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ </body>
+
+ <commandset id="test_commandset"/>
+ <toolbarbutton id="button_test"/>
+
+</window>
diff --git a/dom/xul/test/test_bug391002.xul b/dom/xul/test/test_bug391002.xul
new file mode 100644
index 0000000000..9387caeaf0
--- /dev/null
+++ b/dom/xul/test/test_bug391002.xul
@@ -0,0 +1,41 @@
+<?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=391002
+-->
+<window title="Mozilla Bug 391002"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=391002"
+ target="_blank">Mozilla Bug 391002</a>
+ </body>
+
+ <button id="btn1" command="cmd1"/>
+
+ <button id="btn2">
+ <observes id="observes" element="cmd1" attribute="label"/>
+ </button>
+
+ <commandset>
+ <command id="cmd1" label="cmd1"/>
+ <command id="cmd2" label="cmd2"/>
+ </commandset>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 391002 **/
+
+ $("btn1").setAttribute("command", "cmd2");
+ is($("btn1").getAttribute("label"), $("cmd2").getAttribute("label"))
+
+ $("observes").setAttribute("element", "cmd2");
+ is($("btn2").getAttribute("label"), $("cmd2").getAttribute("label"))
+
+ ]]></script>
+</window>
+
diff --git a/dom/xul/test/test_bug398289.html b/dom/xul/test/test_bug398289.html
new file mode 100644
index 0000000000..4fc36c9234
--- /dev/null
+++ b/dom/xul/test/test_bug398289.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html style="height: 100%">
+<head>
+ <title>Test for bug 398289</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body style="height: 100%" onload="setTimeout(onBodyLoad, 0);">
+ <iframe id="test" src="398289-resource.xul" width="100%" height="100%">
+ </iframe>
+
+ <script class="testbody" type="text/javascript">
+ var snap1, snap2;
+
+ SimpleTest.waitForExplicitFinish();
+
+ function onBodyLoad() {
+ window.frames[0].document.getElementById("test").selectedIndex = 0;
+ window.frames[0].document.getElementById("test").selectedIndex = 1;
+
+ snap1 = snapshotWindow(window);
+
+ document.getElementById("test").onload = onFrameLoad;
+ window.frames[0].location.reload();
+ }
+
+ function onFrameLoad() {
+ snap2 = snapshotWindow(window);
+
+ var equal, str1, str2;
+ [equal, str1, str2] = compareSnapshots(snap1, snap2, true);
+
+ ok(equal, "persistent attribute in tab box broken, expected: "+str1+" got: "+str2);
+
+ SimpleTest.finish();
+ }
+ </script>
+</body>
+</html>
diff --git a/dom/xul/test/test_bug403868.xul b/dom/xul/test/test_bug403868.xul
new file mode 100644
index 0000000000..e82c1f62b4
--- /dev/null
+++ b/dom/xul/test/test_bug403868.xul
@@ -0,0 +1,83 @@
+<?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=403868
+-->
+<window title="Mozilla Bug 403868"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=403868"
+ target="_blank">Mozilla Bug 403868</a>
+ <div id="content" style="display: none"/>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 403868 **/
+function createSpan(id, insertionPoint) {
+ var s = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ s.id = id;
+ $("content").insertBefore(s, insertionPoint);
+ return s;
+}
+
+var s1a = createSpan("test1", null);
+is(document.getElementById("test1"), s1a,
+ "Only one span with id=test1 in the tree; should work!");
+
+var s2a = createSpan("test1", null);
+is(document.getElementById("test1"), s1a,
+ "Appending span with id=test1 doesn't change which one comes first");
+
+var s3a = createSpan("test1", s2a);
+is(document.getElementById("test1"), s1a,
+ "Inserting span with id=test1 not at the beginning; doesn't matter");
+
+var s4a = createSpan("test1", s1a);
+is(document.getElementById("test1"), s4a,
+ "Inserting span with id=test1 at the beginning changes which one is first");
+
+s4a.parentNode.removeChild(s4a);
+is(document.getElementById("test1"), s1a,
+ "First-created span with id=test1 is first again");
+
+s1a.parentNode.removeChild(s1a);
+is(document.getElementById("test1"), s3a,
+ "Third-created span with id=test1 is first now");
+
+// Start the id hashtable
+for (var i = 0; i < 256; ++i) {
+ document.getElementById("no-such-id-in-the-document" + i);
+}
+
+var s1b = createSpan("test2", null);
+is(document.getElementById("test2"), s1b,
+ "Only one span with id=test2 in the tree; should work!");
+
+var s2b = createSpan("test2", null);
+is(document.getElementById("test2"), s1b,
+ "Appending span with id=test2 doesn't change which one comes first");
+
+var s3b = createSpan("test2", s2b);
+is(document.getElementById("test2"), s1b,
+ "Inserting span with id=test2 not at the beginning; doesn't matter");
+
+var s4b = createSpan("test2", s1b);
+is(document.getElementById("test2"), s4b,
+ "Inserting span with id=test2 at the beginning changes which one is first");
+
+s4b.parentNode.removeChild(s4b);
+is(document.getElementById("test2"), s1b,
+ "First-created span with id=test2 is first again");
+
+s1b.parentNode.removeChild(s1b);
+is(document.getElementById("test2"), s3b,
+ "Third-created span with id=test2 is first now");
+
+ ]]></script>
+</window>
diff --git a/dom/xul/test/test_bug414907.xul b/dom/xul/test/test_bug414907.xul
new file mode 100644
index 0000000000..cc4ecedeb4
--- /dev/null
+++ b/dom/xul/test/test_bug414907.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"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=414907
+-->
+<window title="Mozilla Bug 414907"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <vbox hidden="true">
+ <bindings xmlns="http://www.mozilla.org/xbl" style="display: block;"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <binding id="anon">
+ <implementation>
+ <constructor>
+ <![CDATA[
+ var node = this.firstChild;
+ this.palette = node;
+ this.removeChild(node);
+ ]]>
+ </constructor>
+ </implementation>
+ </binding>
+ </bindings>
+ </vbox>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=414907"
+ target="_blank">Mozilla Bug 414907</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function onload() {
+ is($("box").palette.getAttribute("label"), $("command").getAttribute("label"));
+ SimpleTest.finish();
+ }
+
+ ]]></script>
+
+ <command id="command" label="label"/>
+
+ <box id="box" style="-moz-binding:url('#anon');">
+ <button observes="command"/>
+ </box>
+
+
+
+</window>
diff --git a/dom/xul/test/test_bug418216.xul b/dom/xul/test/test_bug418216.xul
new file mode 100644
index 0000000000..e1e42ce113
--- /dev/null
+++ b/dom/xul/test/test_bug418216.xul
@@ -0,0 +1,46 @@
+<?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=418216
+-->
+<window title="Mozilla Bug 418216"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=418216"
+ target="_blank">Mozilla Bug 418216</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 418216 **/
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function onload() {
+ var element = $("item");
+ $("container").removeChild($("item"));
+ $("broadcaster").removeAttribute("disabled");
+ $("container").appendChild(element);
+ is($("item").hasAttribute("disabled"), $("broadcaster").hasAttribute("disabled"));
+ SimpleTest.finish();
+ }
+
+
+
+
+ ]]></script>
+
+<box id="container">
+<textbox id="item">
+ <observes element="broadcaster" attribute="disabled"/>
+</textbox>
+</box>
+
+<broadcasterset>
+ <broadcaster id="broadcaster" disabled="true"/>
+</broadcasterset>
+
+</window>
diff --git a/dom/xul/test/test_bug445177.xul b/dom/xul/test/test_bug445177.xul
new file mode 100644
index 0000000000..ef89b7d871
--- /dev/null
+++ b/dom/xul/test/test_bug445177.xul
@@ -0,0 +1,85 @@
+<?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=445177
+-->
+<window title="Test for Bug 445177"
+ id="test_bug445177_xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ 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 id="body" xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=445177">Mozilla Bug 445177</a>
+
+
+<hbox id="b1" value="foo"/>
+<hbox id="o1" observes="b1"/>
+
+<pre id="test">
+ <script class="testbody" type="text/javascript">
+<![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ function do_test() {
+ var b1 = document.getElementById("b1");
+ var o1 = document.getElementById("o1");
+
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (1)");
+
+ b1.setAttribute("value", "bar");
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (2)");
+
+ b1.removeAttribute("value");
+ is(o1.hasAttribute("value"), b1.hasAttribute("value"), "Wrong value (3)");
+
+ o1.setAttribute("value", "foo");
+ isnot(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (4)");
+
+ b1.setAttribute("value", "foobar");
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (5)");
+
+ //After removing listener, changes to broadcaster shouldn't have any effect.
+ o1.parentNode.removeChild(o1);
+ b1.setAttribute("value", "foo");
+ isnot(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (6)");
+
+ b1.parentNode.appendChild(o1);
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (7)");
+
+ o1.parentNode.removeChild(o1);
+ o1.removeAttribute("observes");
+ o1.removeAttribute("value");
+ b1.parentNode.appendChild(o1);
+ isnot(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (8)");
+
+ document.addBroadcastListenerFor(b1, o1, "value");
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (9)");
+
+ o1.parentNode.removeChild(o1);
+ b1.setAttribute("value", "foobar");
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (10)");
+
+ b1.parentNode.appendChild(o1);
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (11)");
+
+ o1.setAttribute("observes", "b1");
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (12)");
+
+ // When broadcaster isn't in document, changes to its attributes aren't
+ // reflected to listener.
+ b1.parentNode.removeChild(b1);
+ b1.setAttribute("value", "foo");
+ isnot(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (13)");
+
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(do_test);
+]]>
+ </script>
+</pre>
+</body>
+</window>
diff --git a/dom/xul/test/test_bug449457.xul b/dom/xul/test/test_bug449457.xul
new file mode 100644
index 0000000000..0401725f7f
--- /dev/null
+++ b/dom/xul/test/test_bug449457.xul
@@ -0,0 +1,25 @@
+<?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=449457
+-->
+<window title="Mozilla Bug 449457"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=449457"
+ target="_blank">Mozilla Bug 449457</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 449457 **/
+ document.popupNode = document;
+ ok(true, "This is just a leak test");
+
+ ]]></script>
+</window>
diff --git a/dom/xul/test/test_bug468176.xul b/dom/xul/test/test_bug468176.xul
new file mode 100644
index 0000000000..1f3312266e
--- /dev/null
+++ b/dom/xul/test/test_bug468176.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"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=468176
+-->
+<window title="Test for Bug 468176"
+ id="test_bug468176_xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ 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 id="body" xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=468176">Mozilla Bug 468176</a>
+
+<xul:hbox id="b1" value="foo"/>
+
+<xul:hbox id="o1">
+ <xul:observes id="inner" element="b1" attribute="*"/>
+</xul:hbox>
+
+<pre id="test">
+ <script class="testbody" type="text/javascript">
+<![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ var broadcastCount = 0;
+ function b_listener(evt) {
+ ++broadcastCount;
+ }
+
+ function do_test() {
+ var b1 = document.getElementById("b1");
+ var o1 = document.getElementById("o1");
+ var inner = document.getElementById("inner");
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (1)");
+
+ inner.addEventListener("broadcast", b_listener, true);
+ b1.setAttribute("value", "bar");
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (2)");
+ is(broadcastCount, 1, "Wrong value (3)");
+
+ b1.removeAttribute("value");
+ is(o1.hasAttribute("value"), b1.hasAttribute("value"), "Wrong value (4)");
+ is(broadcastCount, 2, "Wrong value (5)");
+
+ o1.setAttribute("value", "foo");
+ isnot(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (6)");
+ is(broadcastCount, 2, "Wrong value (7)");
+
+ b1.setAttribute("value", "foobar");
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (8)");
+ is(broadcastCount, 3, "Wrong value (9)");
+
+ b1.removeAttribute("value");
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (10)");
+ is(broadcastCount, 4, "Wrong value (11)");
+
+ b1.removeAttribute("value");
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (12)");
+ is(broadcastCount, 4, "Wrong value (13)");
+
+ o1.setAttribute("value", "bar");
+ b1.setAttribute("value", "bar"); // This should still dispatch 'broadcast'
+ is(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (14)");
+ is(broadcastCount, 5, "Wrong value (15)");
+
+ //After removing listener, changes to broadcaster shouldn't have any effect.
+ o1.parentNode.removeChild(o1);
+ b1.setAttribute("value", "foo");
+ isnot(o1.getAttribute("value"), b1.getAttribute("value"), "Wrong value (16)");
+ is(broadcastCount, 5, "Wrong value (17)");
+
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(do_test);
+]]>
+ </script>
+</pre>
+</body>
+</window>
diff --git a/dom/xul/test/test_bug486990.xul b/dom/xul/test/test_bug486990.xul
new file mode 100644
index 0000000000..fb375a4cca
--- /dev/null
+++ b/dom/xul/test/test_bug486990.xul
@@ -0,0 +1,155 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=486990
+-->
+<window title="Mozilla Bug 486990"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="setTimeout(runTests, 0);">
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"/>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=486990"
+ target="_blank">Mozilla Bug 486990</a>
+
+ </body>
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <select size="5" id="select">
+ <option>1</option>
+ <option>2</option>
+ <option>3</option>
+ <option>4</option>
+ <option>5</option>
+ <option>6</option>
+ <option>7</option>
+ <option>8</option>
+ <option>9</option>
+ <option>10</option>
+ </select>
+ </div>
+ <menupopup id="cm" onpopupshowing="popupShowing(event);">
+ <menuitem label="Mozilla" value="http://mozilla.org"/>
+ <menuitem label="Slashdot" value="http://slashdot.org"/>
+ <menuitem label="Sourceforge" value="http://sf.net"/>
+ <menuitem label="Freshmeat" value="http://freshmeat.net"/>
+ </menupopup>
+ <button label="test button" contextmenu="cm" id="testbutton"/>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 486990 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ var prevented = false;
+ var eventCount = 0;
+
+ function fooListener(evt) {
+ evt.preventDefault();
+ prevented = evt.defaultPrevented;
+ ++eventCount;
+ };
+
+ var clickCount = 0;
+ var mouseDownCount = 0;
+ var mouseUpCount = 0;
+ function clickListener(evt) {
+ ++clickCount;
+ }
+
+ function mouseDownListener(evt) {
+ ++mouseDownCount;
+ }
+
+ function mouseUpListener(evt) {
+ ++mouseUpCount;
+ }
+
+ var popupshowingcount = 0;
+
+ function popupShowing(evt) {
+ ++popupshowingcount;
+ evt.preventDefault();
+ }
+
+ function contextMenuStopper(evt) {
+ evt.stopPropagation();
+ }
+
+ function contextMenuPreventer(evt) {
+ evt.preventDefault();
+ }
+
+ var tb;
+ function runTests() {
+ document.addEventListener("foo", fooListener, true);
+ var e1 = document.createEvent("Event");
+ e1.initEvent("foo", true, true);
+ document.dispatchEvent(e1);
+ is(eventCount, 1, "Wrong event count");
+ ok(prevented, "Default handling should have been prevented.");
+
+ prevented = false;
+ var e2 = document.createEvent("Event");
+ e2.initEvent("foo", false, false);
+ document.dispatchEvent(e1);
+ is(eventCount, 2, "Wrong event count");
+ ok(prevented, "Default handling should have been prevented.");
+
+ tb = document.getElementById("testbutton");
+ dispatchTrustedContextMenuEvent(tb);
+ is(popupshowingcount, 1, "Should have got 'popupShowing' event!");
+
+ tb.addEventListener("contextmenu", contextMenuStopper, true);
+ dispatchTrustedContextMenuEvent(tb);
+ is(popupshowingcount, 2, "Should have got 'popupShowing' event!");
+
+ tb.addEventListener("contextmenu", contextMenuPreventer, true);
+ dispatchTrustedContextMenuEvent(tb);
+ is(popupshowingcount, 2, "Should not have got 'popupShowing' event!");
+
+ SpecialPowers.pushPrefEnv({"set": [["dom.event.contextmenu.enabled", false]]}, test2);
+ }
+
+ function test2() {
+ dispatchTrustedContextMenuEvent(tb);
+ is(popupshowingcount, 3, "Should have got 'popupShowing' event!");
+
+ SpecialPowers.pushPrefEnv({"set": [["dom.event.contextmenu.enabled", true]]}, test3);
+ }
+
+ function test3() {
+ dispatchTrustedContextMenuEvent(tb);
+ is(popupshowingcount, 3, "Should not have got 'popupshowing' event!");
+
+ var s = document.getElementById("select");
+ s.addEventListener("click", clickListener, true);
+ s.addEventListener("mousedown", mouseDownListener, true);
+ s.addEventListener("mouseup", mouseUpListener, true);
+
+ synthesizeMouse(s, 1, 10, {}, window);
+ is(clickCount, 1, "Should have got click event!");
+ is(mouseDownCount, 1, "Should have got mousedown event!");
+ is(mouseUpCount, 1, "Should have got mouseup event!");
+
+ // Dispatch to scrollbar.
+ synthesizeMouse(s, s.getBoundingClientRect().right - 3, 10, {}, window);
+ is(clickCount, 1, "Should not have got click event!");
+ is(mouseDownCount, 2, "Should have got mousedown event!");
+ is(mouseUpCount, 2, "Should have got mouseup event!");
+
+ SimpleTest.finish();
+ }
+
+ function dispatchTrustedContextMenuEvent(target) {
+ return sendMouseEvent({type:"contextmenu"}, target, window);
+ }
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/xul/test/test_bug497875.xul b/dom/xul/test/test_bug497875.xul
new file mode 100644
index 0000000000..a083daa537
--- /dev/null
+++ b/dom/xul/test/test_bug497875.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"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=497875
+-->
+<window title="Mozilla Bug 497875"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=497875">Mozilla Bug 497875</a>
+<p id="display"><iframe id="iframe" src="bug497875-iframe.xul"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 497875 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function done_test() {
+ var f = document.getElementById('iframe')
+ f.src="about:blank"
+ f.parentNode.removeChild(f)
+ ok(true, "this is a crashtest");
+ SimpleTest.finish();
+}
+
+function do_test() {
+ setTimeout(function () {document.getElementById('iframe').contentWindow.location.reload()}, 500);
+ setTimeout(function () {document.getElementById('iframe').contentWindow.location.reload()}, 1000);
+ setTimeout(function () {document.getElementById('iframe').contentWindow.location.reload()}, 1500);
+ setTimeout(done_test, 2000);
+}
+
+do_test();
+
+
+]]>
+</script>
+
+</window>
diff --git a/dom/xul/test/test_bug583948.xul b/dom/xul/test/test_bug583948.xul
new file mode 100644
index 0000000000..c0d6ebd49a
--- /dev/null
+++ b/dom/xul/test/test_bug583948.xul
@@ -0,0 +1,38 @@
+<?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">
+
+<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <div id="content" style="display: none"/>
+</body>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+var attempts = 0;
+
+function update() {
+ // without the crash fix, this usually crashes after 2 to 4 reloads
+ if (++attempts == 6) {
+ ok(true, "didn't crash after 6 attempts");
+ otherWindow.close();
+ SimpleTest.waitForFocus(function() {
+ SimpleTest.finish();
+ });
+ } else {
+ otherWindow.document.commandDispatcher.updateCommands('');
+ setTimeout(function() {
+ otherWindow.location.reload()
+ }, 0);
+ }
+}
+
+var otherWindow = window.open("window_bug583948.xul", "_new", "chrome");
+</script>
+
+</window>
diff --git a/dom/xul/test/test_bug640158_overlay_persist.xul b/dom/xul/test/test_bug640158_overlay_persist.xul
new file mode 100644
index 0000000000..ad48173313
--- /dev/null
+++ b/dom/xul/test/test_bug640158_overlay_persist.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"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=640158
+-->
+<window title="Mozilla Bug 640158" id="rootwin"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=640158"
+ target="_blank">Mozilla Bug 640158</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function onload() {
+ is($("foo").getAttribute("testattr"), "original", "Attribute should be in original state");
+ // Change and persist another value:
+ $("foo").setAttribute("testattr", "changed");
+ document.persist("foo", "testattr");
+ $("foo").setAttribute("testattr", "original");
+
+ // Hacky times: check that items which are overlaid do get persisted into correctly,
+ // by first creating an extra element and persisting the value before loading an
+ // overlay that changes that value - the persisted value should be reinstated.
+ let root = document.documentElement;
+ let bar = document.createElement("box");
+ bar.id = "bar";
+ bar.setAttribute("testattr", "changed"); // The overlay we load has 'original'
+ root.appendChild(bar);
+ document.persist("bar", "testattr");
+ document.loadOverlay(location.href.replace(/[^\\\/]*.xul/, "overlay_bug640158.xul"), function() {
+ is($("foo").getAttribute("testattr"), "original",
+ "Non-overlaid attribute should still be in original state");
+ is($("bar").getAttribute("testattr"), "changed",
+ "Overlaid attribute should have been changed.");
+ SimpleTest.finish();
+ });
+ }
+
+ ]]></script>
+
+ <box id="foo" testattr="original"/>
+
+</window>
+
diff --git a/dom/xul/test/test_bug749367.xul b/dom/xul/test/test_bug749367.xul
new file mode 100644
index 0000000000..478a119e82
--- /dev/null
+++ b/dom/xul/test/test_bug749367.xul
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=749367
+-->
+<window title="Mozilla Bug 749367"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="setTimeout(runTests, 0);">
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=486990"
+ target="_blank">Mozilla Bug 486990</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="text/template">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ function runTests() {
+ ok(false, "Shouldn't execute");
+ SimpleTest.finish();
+ }
+ ]]>
+ </script>
+
+ <script type="text/javascript">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ function runTests() {
+ ok(true, "Should execute");
+ SimpleTest.finish();
+ }
+ ]]>
+ </script>
+
+</window>
diff --git a/dom/xul/test/test_bug757137.xul b/dom/xul/test/test_bug757137.xul
new file mode 100644
index 0000000000..e44a160b32
--- /dev/null
+++ b/dom/xul/test/test_bug757137.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"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <div id="content" style="display: none"/>
+</body>
+
+<script>
+var Ci = Components.interfaces;
+var chromeWindowInterface = Ci.nsIDOMChromeWindow;
+
+SimpleTest.waitForExplicitFinish();
+
+// Force off out-of-process mozbrowser because we need to grab its
+// |window| synchronously from here. Out-of-process docshell creation
+// for mozbrowser haves entirely differently.
+// XXX why doesn't pushPrefEnv() work here?
+var otherWindow = window.open("window_bug757137.xul", "", "chrome");
+ok(chromeWindowInterface !== null, 'nsIDOMChromeWindow interface is defined');
+var otherChromeWindow = null;
+try {
+ otherChromeWindow = otherWindow.QueryInterface(chromeWindowInterface);
+} catch(e) {
+ ok(false, 'exception when QI to ChromeWindow');
+}
+ok(otherChromeWindow !== null, 'XUL window should QI to ChromeWindow');
+
+SpecialPowers.pushPrefEnv({"set":[["dom.ipc.tabs.disabled", true]]}, startTest);
+
+function startTest() {
+ otherWindow.onload = function () {
+ var w = otherWindow.document.getElementById('f').contentWindow;
+ ok(w !== null, 'got the |window| for a mozbrowser iframe');
+ var chromeWindow = null;
+ try {
+ var chromeWindow = w.QueryInterface(chromeWindowInterface);
+ } catch(e) { }
+ ok(chromeWindow === null, 'mozbrowser iframe should not get ChromeWindow');
+
+ otherWindow.close();
+ SimpleTest.waitForFocus(SimpleTest.finish);
+ };
+}
+</script>
+
+</window>
diff --git a/dom/xul/test/test_bug775972.xul b/dom/xul/test/test_bug775972.xul
new file mode 100644
index 0000000000..eeb7ebdac3
--- /dev/null
+++ b/dom/xul/test/test_bug775972.xul
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=775972
+-->
+<window title="Mozilla Bug 775972"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="test()">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=775972"
+ target="_blank">Mozilla Bug 775972</a>
+ </body>
+
+ <hbox id="container"><label value="test" id=""/></hbox>
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 775972 **/
+
+ function test() {
+ var c = document.getElementById("container");
+ var clone = c.cloneNode(true);
+ document.documentElement.appendChild(clone);
+ ok(true, "This shouldn't crash!");
+ }
+
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/xul/test/test_import_xul_to_content.xul b/dom/xul/test/test_import_xul_to_content.xul
new file mode 100644
index 0000000000..a0b26fa969
--- /dev/null
+++ b/dom/xul/test/test_import_xul_to_content.xul
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<window title="Mozilla Importing XUL into Content"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1027299"
+ target="_blank">Mozilla Bug 1027299</a>
+ </body>
+
+ <browser id="browserelt" src="about:blank" type="content"/>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ Components.utils.import("resource://gre/modules/Services.jsm");
+
+ SimpleTest.waitForExplicitFinish();
+
+ function expectWarning(expected, when, f) {
+ Services.console.reset();
+
+ f();
+
+ var sawWarning = false;
+ var msgs = Services.console.getMessageArray();
+ for (var i = 0; i < msgs.length; i++) {
+ var msg = msgs[i];
+ if (!msg || !(msg instanceof Components.interfaces.nsIScriptError)) {
+ continue;
+ }
+
+ if (msg.category.includes("DOM") && msg.errorMessage.includes("Importing XUL")) {
+ sawWarning = true;
+ }
+ }
+
+ ok(sawWarning == expected, "correct warning condition when " + when);
+ }
+
+ var browser = document.getElementById("browserelt");
+ browser.addEventListener("load", function() {
+ var doc = browser.contentDocument;
+
+ // We add a <video> element, which contains anonymous XUL content. This should not warn.
+ var video = doc.createElement("video");
+ expectWarning(false, "appending video", function() {
+ doc.documentElement.appendChild(video);
+ // Force a layout flush to make sure the anonymous content is added.
+ let dummy = doc.documentElement.offsetLeft;
+ });
+
+ // We add some XUL to a content document. This should generate a warning.
+ var elt = document.createElement("label");
+ var newElt = doc.importNode(elt, false);
+ expectWarning(true, "appending XUL", function() {
+ doc.documentElement.appendChild(newElt);
+ });
+
+ SimpleTest.finish();
+ });
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/xul/test/window_bug583948.xul b/dom/xul/test/window_bug583948.xul
new file mode 100644
index 0000000000..d0f6a26926
--- /dev/null
+++ b/dom/xul/test/window_bug583948.xul
@@ -0,0 +1,8 @@
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="opener.update()">
+
+<command oncommandupdate="document.removeChild(document.documentElement)" commandupdater="true"/>
+<box command="c"/>
+<iframe/>
+
+</window>
diff --git a/dom/xul/test/window_bug757137.xul b/dom/xul/test/window_bug757137.xul
new file mode 100644
index 0000000000..16f38ef20f
--- /dev/null
+++ b/dom/xul/test/window_bug757137.xul
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+ <html:iframe id="f" mozbrowser="true"
+ src="data:text/html;charset=utf-8,%3C!DOCTYPE html>Hi" />
+</window>