views:

2161

answers:

4

If you open a text file (.txt, .js, .css, ...) in your browser, it will get wrapped up in a nice DOM tree.

For example, open this .txt file and enter

javascript:alert(document.documentElement.innerHTML);

into your address bar. Nice... every major browser supports DOM manipulation on this wrapped text files, which is a great thing for writing powerful bookmarklets or user scripts.

However, Firefox fails on assigning any element's innerHTML. For example,

javascript: document.body.innerHTML = document.body.innerHTML.replace(/(\d+\s+\w+(?=\s+\d+))/g, '<span style="color:red">$1</span>'); void 0;

will work in every browser but Firefox.

Is there a trick to work around this issue?

(No, I don't want to parse the innerHTML string manually, and no, it doesn't work with jQuery either.)

A: 

use GreaseMonkey

Itay Moav
+1  A: 

It is failing because there is no body - even the file you linked is just a text file without a body (perhaps you are looking at it in firebug?).

The best thing to do would be a regex replace since you are working with text.

Andrew Hare
A: 

It seems on a text document in Firefox 3, assigning the innerHTML of any node acts as if you are assigning to innerText (with “<html><body><pre>” prepended).

(Since DOM scripting on a non-XML/HTML document is completely undefined, it's certainly within Firefox's rights to do this; it appears to be a quick hack to display text files in an HTML page.)

So you can't use innerHTML on Firefox, but other DOM methods work:

var span= createElement('span');
span.style.color= 'red';
span.appendChild(document.createTextNode(match));
bobince
Right. The thing is, to get this working, you need a parser. Either- use one written in JavaScript (e.g. http://ejohn.org/blog/pure-javascript-html-parser/ ) which is slow and bulky, or- find a way to get the parsing done by Firefox, which I prefer
Pumbaa80
Not really, you could just search over the text node data and use splitText() to split out each match you find and insert the above span element. No parsing involved.
bobince
+1  A: 

I think I found a working solution. First of all, let me give some more details on the question.

The problem is: Firefox creates something like

[some wrapper]
+---document
    +---<html>[=documentElement]
        +---<body>
            +---<head/>
            +---<pre>
                +---[actual plain text contents]

but the wrapped document object does not support setting innerHTML properly. So, the basic idea is, create a new document object with full innerHTML support. Here's how it works:

var setInnerHTML = function(el, string) {
    if (typeof window.supportsInnerHTML == 'undefined') {
        var testParent = document.createElement('div');
        testParent.innerHTML = '<br/>';
        window.supportsInnerHTML = (testParent.firstChild.nodeType == 1);
    }
    if (window.supportsInnerHTML) {
        el.innerHTML = string;
    } else {
        if (!window.cleanDocumentObject) {
            /* this is where we get a 'clean' document object */
            var f = document.createElement('iframe');
            f.style.setProperty('display', 'none', 'important');
            f.src = 'data:text/html,<!DOCTYPE html><html><title></title></html>';
            document.body.appendChild(f); /* <- this is where FF creates f.contentDocument */
            window.cleanDocumentObject = f.contentDocument;
            document.body.removeChild(f);
        }

        /* let browser do the parsing */
        var div = window.cleanDocumentObject.createElement('div');
        div.innerHTML = string; /* this does work */

        /* copy childNodes */
        while(el.firstChild) {
            el.removeChild(el.firstChild); /* cleanup */
        }
        for (var i = 0; i < div.childNodes.length; i++) {
            el.appendChild(div.childNodes[i].cloneNode(true));
        }
        delete div;
    }
}


edit:

This version is better and faster; using XSLTProcessor instead of iFrame.

var setInnerHTML = function(el, string) {
    // element.innerHTML does not work on plain text files in FF; this restriction is similar to
    // http://groups.google.com/group/mozilla.dev.extensions/t/55662db3ea44a198
    var self = arguments.callee;
    if (typeof self.supportsInnerHTML == 'undefined') {
        var testParent = document.createElement('div');
        testParent.innerHTML = '<p/>';
        self.supportsInnerHTML = (testParent.firstChild.nodeType == 1);
    }
    if (self.supportsInnerHTML) {
        el.innerHTML = string;
        return el;
    } else if (typeof XSLTProcessor == 'undefined') {
        return undefined;
    } else {
        if (typeof self.cleanDocument == 'undefined')
            self.cleanDocument = createHTMLDocument();

        if (el.parentNode) {
            var cleanEl = self.cleanDocument.importNode(el, false);
            cleanEl.innerHTML = string;
            el.parentNode.replaceChild(document.adoptNode(cleanEl), el);
        } else {
            var cleanEl = self.cleanDocument.adoptNode(el);
            cleanEl.innerHTML = string;
            el = document.adoptNode(cleanEl);
        }

        return el;
    }

    function createHTMLDocument() {
        // Firefox does not support document.implementation.createHTMLDocument()
        // cf. http://www.quirksmode.org/dom/w3c_html.html#t12
        // the following is taken from http://gist.github.com/49453
        var xmlDoc = document.implementation.createDocument('', 'fooblar', null);
        var templ = '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;'
                + '<xsl:output method="html"/><xsl:template match="/">'
                + '<html><title/><body/></html>'
                + '</xsl:template></xsl:stylesheet>';
        var proc = new XSLTProcessor();
        proc.importStylesheet(new DOMParser().parseFromString(templ,'text/xml'));
        return proc.transformToDocument(xmlDoc);
    }
};
Pumbaa80