This guide describes how different of areas can influence the performance of the editor and what you can do to optimize those areas. It goes into how XPath can be optimized to improve your editor and suggests some other features to use make make the editor perform better.

XPaths and performance

Fonto Editor uses XPaths for a lot of APIs. Most of these trigger an update when the result changes because of user input. Fonto Editor does this by recording which DOM properties are accessed by an XPath query.

We have explained our approach in more detail during XML Prague 2017.

Fonto Editor does not yet automatically speed up any XPath queries, though this is planned for a future release. If an instance experiences performance related issues, it may be worth the effort to manually go over the registered XPaths to optimize them.

To guarantee an acceptable performance, some manual optimizations may be needed to prevent an XPath query from accessing too many DOM properties.

To get more insight into how XPath influence the performance of your editor, take a look at startProfilingXPathPerformance and related APIs. This blog post is another valuable resource to learn how we used the XPath profiler to improve the performance of a specific editor that handles large documents.

Avoid full document scans

Full document scans (or traversing a large part of the document) should be avoided, unless they are absolutely necessary. If a developer knows a certain element can only occur in a certain subtree of the document, it is more efficient to only traverse that part. For example: If the element articleNumber can only occur as a descendant of /metadata/article/otherInfo, the XPath query /metadata/article/otherInfo/descendant::articleNumber will perform better than //articleNumber.

Ordering of operands

Our XPath implementation evaluates binary boolean operands (and, or) left to right. This can be leveraged to optimize some XPath queries.


self::someElement[(let $id := @id return //@idref=$id) and @needsRef]

This XPath needs a full document scan, even if the @needsRef attribute is absent. By swapping the operands around, the XPath can skip the next operand.


self::someElement[@needsRef and (let $id := @id return //@idref=$id)]

If the instance performs normally, but only experiences a slowdown when changing the children of an element, it could very well be a pattern like this.

Attribute Index

An attribute index allows quick lookup of an element by the value of one of its attributes. This will create a index of all elements with that attribute, which will do a full document scan at startup of the application and be kept up to date by tracking changes made to the document. See addAttributeIndex for configuration and how to use.

We recommend introducing an attribute index to prevent full document scans. Instead of writing //[@id=$rid] you can prevent the full document scan by adding a index on the id attribute and writing my-namespace:id($id).


Especially with CVK and SVK configuration, Fonto Editor tries to bucket XPath selectors by the type of node they'll match. When examining the CVK properties of a node, only selectors in buckets which are applicable for the given element are evaluated. For example: the selector self::processing-instruction() never has to be evaluate for whichever element and the selector self::p[..] will always return false when passed a <div> element, the full XPath engine does not have to be started for this selector. For some instances, this reduces a list of hundreds of selectors to a list of only a few.

Currently, buckets are determined by examining the compiled selector. This basically attempt to find the following patterns:


The selector is placed in the bucket for the specified nodeName. This is the preferred bucket, since it has the smallest granularity. Please note that the selector self::*[self::<nodeName>] will be placed into the element type bucket and the selector name() = "<nodeName>" will be placed into the global bucket, causing it to be evaluated when examining any element.

self::* or self::element()

The selector is put in the elements bucket. It will only be executed when examining an element.

self::processing-instruction(), self::comment(), etc

The selector is placed into the correct bucket for the nodeType. This causes it to be evaluated for any other selector.

<subExpression> or <subExpression>

The selector is placed in the bucket of the sub expressions if both have the same bucket. The selector has no bucket if they differ.

<subExpression> and <subExpression>

The selector is placed in the bucket of any of the subExpressions with a bucket.


The selector is placed into the bucket of the subExpression.

Most other expressions: name() = "<nodeName>", ancestor::<ancestor>

The selector can not be placed in any bucket. It may be executed for any node. This should be avoided if possible.

Toolbar tabs and performance

A toolbar tab contains multiple buttons, drops, and other UI components. These control operations, which in turn can control commands. To see whether these commands are enabled, they are executed in a sandboxed environment. The result of the command is then discarded.

Executing commands can potentially be hard, even more so in large documents. Though the editor is heavily optimized, having to compute the state of a lot of buttons will cost some time.

The editor has to compute the state of every (visible) button in the current toolbar tab. A simple way of mitigating some performance issues is to spread buttons over multiple toolbar tabs, or placing them behind drop-downs.

Example: Operation editing the order of a section-like node

When an operation can be used to reorder nodes, it implies the list of section-like nodes next to it can grow to a large number. This causes a large part of the document to be validated. This is a usual cause of performance loss. This can be mitigated by placing the operation into the structure view as a contextual operation, ensuring its state is only recomputed when the button is about to be used.

Collapsible Tables

Tables are often one of the most performance demanding elements on a document, for this reason it is advised to use collapsible tables whenever possible. Collapsible tables are evaluated Just-In-Time and are only evaluated when they are not collapsed. This means that when tables are collapsed, the document does not experience any performance demands from those tables. This can drastically improve the overall editor performance depending on how the tables were being used.

Refer to this guide on how to configure and use collapsible tables.

Other performance improvements

  • It is advised to use label query widgets instead of custom widgets whenever possible. They are evaluated lazily and are only calculated when they are in view and in doing so they provide a better editor experience and lower initial document load time than custom widgets.

Experimental performance improvements

Apart from reorganizing the toolbar, it is also possible to improve typing performance by enabling a number of experiments. This set changes every release, and some experiments are automatically integrated after some time.

An overview of all current experiments is also available.