Search code examples
javascriptfirefoxdocumentfragmentmithril.js

Firefox empties documentFragment on clone


I've found this strange scenario where Firefox seems to loose the content of a documentFragment after cloning it (with the deep flag set to true). Is this a Firefox bug, or am I missing an implementation detail?

var n = ( function nScope(){
	'use strict';

	function isDom( x ){
		return x.nodeType > 0;
	}

	function notDom( x ){
		return !isDom( x );
	}

	return function n(){
		// Avoids conditional logic later by forcing a standard output.
		var args   = [].map.call( arguments, function wrapDom( x ){
			return isDom( x ) ? [ x ] : x;
		} );
		var dom    = [];
		var vdom   = [];
		// Render virtual DOM, then parse output.
		var view   = m.apply( void 0, args );
		var cfg    = view.attrs.config;

		if( view.children.forEach ){
			view.children.forEach( function divideChildren( x ){
				( isDom( x ) ? dom : vdom ).push( x );
			} );
		}

		if( dom.length === 0 ){
			return view; 
		}

		view.attrs.config = function appendDom( el, init, context ){
			// Only perform DOM insertion logic at config time:
			// Saves unnecessary execution during strategy none redraws.
            if( !init ){
                dom.forEach( function appendNode( node, index ){
                    // If a virtual DOM element occurs after the real node in the children list, grab it and find its
                    // index in the list of virtual elements as a reference point for inserting the real node.
                    var insertAt = vdom.indexOf( view.children.slice( view.children.indexOf( node ) ).filter( notDom )[ 0 ] );
                    // When a documentFragment is inserted into the document, the reference becomes empty.
                    // Therefore we need to insert clones of the original reference.
                    // Because this happens on every redraw, this means DOM nodes cannot be modified by prior reference
                    // between redraw cycles :(
                    console.log( 'Original node:', node );

                    var clone   = node.cloneNode( true );

                    console.log( 'Cloned node:', clone );

                    
                    if( insertAt ){
                        el.insertBefore( clone, el.childNodes[ insertAt ] );
                    }
                    else {
                        el.appendChild( clone );
                    }
                } );
            }

			if( cfg ){
				return cfg( el, init, context );
			}
		};

		// Make sure only the virtual elements are parsed by m.render.
		view.children = vdom;

		return view;
	};
}() );

// To stop jsfiddle breaking
m.route.mode = 'hash';

var links = document.createDocumentFragment();
var array = [ 1, 2 ];

array.forEach( function appendLink( index ){
    var a  = document.createElement( 'a' );
    
    a.innerText = 'Page ' + index;
    a.href      = '/route' + index;
    
    m.route( a );
    
    links.appendChild( a );
    links.appendChild( document.createTextNode( ' ' ) );
} );

var modules = array.map( function makeModule( index ){
    return {
        controller : function(){},
        view       : function(){
            return n(
                '.module',
                [
                    n( 'h1', {
                        onclick : function(){
                            alert( 'Redraw incoming...' );
                        }
                    }, 'Page ' + index ),
                    links 
                ]
            );
        }
    };
} );

m.route( document.body, '/route1', {
    '/route1' : modules[ 0 ],
    '/route2' : modules[ 1 ]
} );
<script src="https://rawgit.com/lhorie/mithril.js/master/mithril.js"></script>


Solution

  • You're using innerText, which is not supported by Firefox: 'innerText' works in IE, but not in Firefox

    You could use textContent or innerHTML instead

    array.forEach( function appendLink( index ){
        var a  = document.createElement( 'a' );
    
        a.textContent = 'Page ' + index; // <-- here
        a.href      = '/route' + index;
    
        m.route( a );
    
        links.appendChild( a );
        links.appendChild( document.createTextNode( ' ' ) );
    } );