After some substantial investigation, I've found a workaround and a possible solution and determined that the issue is somewhere in our own bridge. However, it's still not clear where exactly the issue is produced.
First of all, this issue seems to only affect <br/> tags. I tried using <span>, <button> and <hr> tags, and they don't get duplicated. I also specified this markup right in the xhtml document (as opposed to retrieving it from a bean property), using escaped characters, and the issue can be reproduced as well. In both cases, the response is correct: there are no duplicate tags. They get duplicated when they are taken from the response and inserted into the DOM. Also, it's important to clarify that this happens when the content of the second page is loaded dynamically (asynchronously, without actually navigating to a different URL). It doesn't happen when one navigates directly to the second page (i.e. synchronous update).
Going back in revisions, this issue is first seen at revision 44114, which is a commit made for
ICE-10479, as Arran points out. However, this applies only to this specific test application, since the issue can be reproduced with different markup and using the code from P02 and even P01, and maybe from earlier versions as well.
The commit mentioned above simply adds icefaces-compat.js and compat.js as resource dependencies to ice:outputText. Many other extended components already had these resource dependencies before that commit, so this issue can be reproduced using other ice:* components instead of ice:outputText. Using an empty ice:panelgroup causes the same issue, even with jars from P02.
The specific cause of the above is simple. When there's an ice:* component (with the resource dependencies mentioned above) in the second page, there's a full body update. The single <update> tag in the response has the ID 'javax.faces.ViewBody', whereas when there are no ice:* components, the individual nodes are updated separately (i.e. multiple <update> tags). So, when the update is for 'javax.faces.ViewBody', the <br> tags get duplicated.
The reason why a full-body update is triggered when having certain ice:* tags in the second page is that there's a new node insertion, which is a direct child of the body, and because of that, the domdiff causes an update of the whole body (which is the nearest element with a client ID). The node in question is a <span> that contains a <script> and has the ID '********_icefaces_compat_config'.
So, a quick workaround is to include an ice:* component in the first page, so this node with the ID '********_icefaces_compat_config' is already there and can be individually updated by looking up its ID, instead of having to update the whole body.
A possible fix is to make sure to always include this (empty) node mentioned above, even when there are no ice:* components on the page, in order to avoid full-body updates when an ice:* component is introduced. We already do that for the node with the ID 'dynamic-code-compat'.
The above solution can solve this specific problem of introducing ice:* components in the page. However, there are still ways of reproduce this issue. I'm attaching another test page (lftest3.xhtml), which can be simply inserted into the test application. This page doesn't have any ice:* or ace:* component, just standard h:* components. This page has a boolean checkbox, which is used to render/unrender an h:panelGroup, which is a direct child of h:body. This panel group is initially unrendered, and when the checkbox is checked, this panel group is rendered, and because it's a direct child of the body, this triggers a full body update. By examining the markup, you can see that the <br> tags inside this panel group are duplicated as well. This can be reproduced with eh P01 jars as well.
Unfortunately, the issue seems to be in our own bridge. I created a pure-JSF test application (attached) with only this page in it, and the issue couldn't be reproduced: the <br> tags weren't duplicated.
I looked at the code in jsf.js and bridge.js (the uncompressed versions), and I couldn't find a point where tags are explicitly duplicated. It must be a side effect of some other handling or parsing.