Mypal/dom/xul/templates/nsXULContentBuilder.cpp
2019-03-11 13:26:37 +03:00

1977 lines
69 KiB
C++

/* -*- 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;
}