Bug #5458


Trying to replace contents of HTML document's root element html with xsl:result-document href="?." method="ixsl:replace-content" creates or leaves duplicated/empty head/body elements

Added by Martin Honnen 5 months ago. Updated 5 months ago.

IXSL extensions
Start date:
Due date:
% Done:


Estimated time:
Applies to JS Branch:
2, Trunk
Fix Committed on JS Branch:
2, Trunk
Fixed in JS Release:
SEF Generated with:
Contact person:
Additional contact persons:


I have tried to see whether Saxon-JS 2.3 allows to replace the contents of the html root element of an HTML DOM document loaded in the browser, using e.g. xsl:result-document href="?." method="ixsl:replace-content":

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl=""

  <xsl:param name="counter" as="xs:integer" select="1"/>

  <xsl:template match="html">
    <xsl:result-document href="?." method="ixsl:replace-content">

  <xsl:template match="title/text()">
    <xsl:value-of select="replace(., '[0-9]+', string((//input[@id = 'button1'] => ixsl:get('dataset'))?counter + 1))"/>
  <xsl:template match="input[@type = 'button']/@value">
    <xsl:attribute name="{name()}" select="'test ' || (.. => ixsl:get('dataset'))?counter + 1"/>

  <xsl:template match="input[@type = 'button']/@data-counter">
    <xsl:attribute name="{name()}" select=". + 1"/>

  <xsl:output method="html" html-version="5"/>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:template match="input[@type = 'button' and @id = 'button1']" mode="ixsl:onclick">
     <xsl:apply-templates select="/html"/>

  <xsl:template match="/" name="xsl:initial-template">
    <p>Run with {system-property('xsl:product-name')} {system-property('xsl:product-version')} {system-property('Q{}platform')} at {saxon:timestamp()}.</p>
    <input type="button" id="button1" value="test {$counter}" data-counter="{$counter}"/>


but I notice, that somehow, the new contents I want to insert to replace the old, is added to the DOM tree, but after each replace there are empty head and body elements inserted or left. That way the whole approach doesn't work well as the duplicated head and body (those stray empty ones and the newly inserted ones) mess up the rendering of the page.

Example test page is at, it uses above stylesheet which on the initial load and run through the script inserts some button you can click to test the attempt to replace the contents of the html element; if you open the browser console and the tree view you can see that after each click the html element node has additional empty head and body elements before the head and body with the contents the stylesheet attempts to insert. Tests where done with Google Chrome.

Used files are at,,

Actions #1

Updated by Martin Honnen 5 months ago

I have now also tested with Firefox, it shows the same empty head and body elements showing up after each insert in the DOM tree. Somehow its rendering of the rest of the (newly inserted) contents isn't hampered by those elements, as it is in Google Chrome.

Actions #2

Updated by Norm Tovey-Walsh 5 months ago

  • Status changed from New to In Progress
  • Assignee set to Norm Tovey-Walsh
Actions #3

Updated by Norm Tovey-Walsh 5 months ago

My first thought was that it was some odd interaction between the replacement and the appendToBody setting for destination, but that doesn't seem to be it.

Actions #4

Updated by Norm Tovey-Walsh 5 months ago

When you specify replace-content, we use target.innerHTML="" to clear the contents of the target before appending to it. It appears that html.innerHTML="" magically adds an empty head and an empty body because we live in a universe devoid of sense and reason.

This doesn't happen if the elements are removed with removeChild. We could

  1. Always use removeChild, but I don't know if that carries any sort of performance impact
  2. Only use removeChild when the element is html
  3. Document that you can't replace the contents of the html element directly, you have to do the head and body separately.

I don't think choice 3 is very good.

Aaaand a few minutes of web searching suggest that removeChild is faster.

So it's 1, I think.

Actions #5

Updated by Martin Honnen 5 months ago

Using target.textContent = "" is another option, it seems, in Chrome and Firefox it removes any child nodes but doesn't try to "fix" the document structure by adding empty head and body elements.

Actions #6

Updated by Norm Tovey-Walsh 5 months ago

  • Status changed from In Progress to Resolved
  • Applies to JS Branch Trunk added
  • Fix Committed on JS Branch 2, Trunk added

I replaced the innerHTML approach with a simple removeChild loop. New unit test added. (Thanks, Martin!)

Actions #7

Updated by Debbie Lockett 5 months ago

  • Status changed from Resolved to Closed
  • % Done changed from 0 to 100
  • Fixed in JS Release set to SaxonJS 2.4

Bug fix applied in the SaxonJS 2.4 maintenance release.

Please register to edit this issue

Also available in: Atom PDF Tracking page