summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core/document.c79
-rw-r--r--src/core/document.h3
-rw-r--r--src/core/node.c35
3 files changed, 109 insertions, 8 deletions
diff --git a/src/core/document.c b/src/core/document.c
index ff9e5ec..d238558 100644
--- a/src/core/document.c
+++ b/src/core/document.c
@@ -138,8 +138,10 @@ dom_exception dom_document_create(struct dom_implementation *impl,
}
}
- /* Initialise base class */
- err = dom_node_initialise(&d->base, d, DOM_DOCUMENT_NODE,
+ /* Initialise base class -- the Document has no parent, so
+ * destruction will be attempted as soon as its reference count
+ * reaches zero. */
+ err = dom_node_initialise(&d->base, NULL, DOM_DOCUMENT_NODE,
NULL, NULL);
if (err != DOM_NO_ERR) {
/* Clean up interned strings */
@@ -168,6 +170,79 @@ dom_exception dom_document_create(struct dom_implementation *impl,
}
/**
+ * Destroy a document
+ *
+ * \param doc The document to destroy
+ *
+ * The contents of ::doc will be destroyed and ::doc will be freed.
+ */
+void dom_document_destroy(struct dom_document *doc)
+{
+ struct dom_node *c, *d;
+
+ /* Destroy children of this node */
+ for (c = doc->base.first_child; c != NULL; c = d) {
+ d = c->next;
+
+ /* Detach child */
+ c->parent = NULL;
+
+ if (c->refcnt > 0) {
+ /* Something is using this child */
+
+ /** \todo add to list of nodes pending deletion */
+
+ continue;
+ }
+
+ /* Detach from sibling list */
+ c->previous = NULL;
+ c->next = NULL;
+
+ dom_node_destroy(c);
+ }
+
+ /** \todo Ensure list of nodes pending deletion is empty. If not,
+ * then we can't yet destroy the document (its destruction will
+ * have to wait until the pending nodes are destroyed) */
+
+ /* Ok, the document tree is empty, as is the list of nodes pending
+ * deletion. Therefore, it is safe to destroy the document. */
+
+ /* Destroy the doctype (if there is one) */
+ if (doc->type != NULL) {
+ ((struct dom_node *) doc->type)->parent = NULL;
+
+ dom_node_destroy((struct dom_node *) doc->type);
+ }
+
+ doc->type = NULL;
+
+ if (doc->impl != NULL)
+ dom_implementation_unref(doc->impl);
+ doc->impl = NULL;
+
+ /* This is paranoia -- if there are any remaining nodelists or
+ * namednodemaps, then the document's reference count will be
+ * non-zero as these data structures reference the document because
+ * they are held by the client. */
+ doc->nodelists = NULL;
+ doc->maps = NULL;
+
+ /* Clean up interned strings */
+ for (int i = 0; i <= DOM_NODE_TYPE_COUNT; i++) {
+ if (doc->nodenames[i] != NULL)
+ dom_string_unref(doc->nodenames[i]);
+ }
+
+ /* Finalise base class */
+ dom_node_finalise(doc, &doc->base);
+
+ /* Free document */
+ doc->alloc(doc, 0, doc->pw);
+}
+
+/**
* Retrieve the doctype of a document
*
* \param doc The document to retrieve the doctype from
diff --git a/src/core/document.h b/src/core/document.h
index 6561035..367b1ec 100644
--- a/src/core/document.h
+++ b/src/core/document.h
@@ -19,6 +19,9 @@ struct dom_node;
struct dom_nodelist;
struct dom_string;
+/* Destroy a document */
+void dom_document_destroy(struct dom_document *doc);
+
/* Get base of document buffer */
const uint8_t *dom_document_get_base(struct dom_document *doc);
diff --git a/src/core/node.c b/src/core/node.c
index db0fb05..3ccf869 100644
--- a/src/core/node.c
+++ b/src/core/node.c
@@ -39,10 +39,11 @@ void dom_node_destroy(struct dom_node *node)
struct dom_document *owner = node->owner;
/* This function simply acts as a central despatcher
- * for type-specific destructors. It claims a reference upon the
- * owning document during destruction to ensure that the document
- * doesn't get destroyed before its contents. */
+ * for type-specific destructors. */
+ /* Claim a reference upon the owning document during destruction
+ * to ensure that the document doesn't get destroyed before its
+ * contents. */
dom_node_ref((struct dom_node *) owner);
switch (node->type) {
@@ -74,7 +75,7 @@ void dom_node_destroy(struct dom_node *node)
dom_comment_destroy(owner, (struct dom_comment *) node);
break;
case DOM_DOCUMENT_NODE:
- /** \todo document node */
+ dom_document_destroy((struct dom_document *) node);
break;
case DOM_DOCUMENT_TYPE_NODE:
/** \todo document type node */
@@ -88,6 +89,9 @@ void dom_node_destroy(struct dom_node *node)
break;
}
+ /* Release the reference we claimed on the document. If this is
+ * the last reference held on the document and the list of nodes
+ * pending deletion is empty, then the document will be destroyed. */
dom_node_unref((struct dom_node *) owner);
}
@@ -124,7 +128,24 @@ dom_exception dom_node_initialise(struct dom_node *node,
node->next = NULL;
node->attributes = NULL;
- dom_node_ref((struct dom_node *) doc);
+ /* Note: nodes do not reference the document to which they belong,
+ * as this would result in the document never being destroyed once
+ * the client has finished with it. The document will be aware of
+ * any nodes that it owns through 2 mechanisms:
+ *
+ * either a) Membership of the document tree
+ * or b) Membership of the list of nodes pending deletion
+ *
+ * It is not possible for any given node to be a member of both
+ * data structures at the same time.
+ *
+ * The document will not be destroyed until both of these
+ * structures are empty. It will forcibly attempt to empty
+ * the document tree on document destruction. Any still-referenced
+ * nodes at that time will be added to the list of nodes pending
+ * deletion. This list will not be forcibly emptied, as it contains
+ * those nodes (and their sub-trees) in use by client code.
+ */
node->owner = doc;
/** \todo Namespace handling */
@@ -170,7 +191,9 @@ void dom_node_finalise(struct dom_document *doc, struct dom_node *node)
if (node->namespace != NULL)
dom_string_unref(node->namespace);
- dom_node_unref((struct dom_node *) node->owner);
+ /** \todo check if this node is in list of nodes pending deletion.
+ * If so, it must be removed from the list, so the document gets
+ * destroyed once the list is empty (and no longer referenced) */
node->owner = NULL;
/* Paranoia */