The Code
This user script runs on all pages. To ensure that it does not affect URLs that are already linked, it uses an XPath query that includes not(ancestor::a). To ensure that it does affect URLs in uppercase, the XPath query also includes "contains(translate(., 'HTTP', 'http'), 'http')]".
Once we find a text node that definitely contains an unlinked URL, there could be more than one URL within it, so we need to convert all the URLs while keeping the surrounding text intact. We replace the text with an empty <span> element as a placeholder and then incrementally reinsert each non-URL text snippet and each constructed URL link.
Save the following user script as linkify.user.js:
// ==UserScript==
// @name Linkify
// @namespace http://youngpup.net/userscripts
// @description Turn plain-text URLs into hyperlinks
// @include *
// ==/UserScript==
// based on code by Aaron Boodman
// and included here with his gracious permission
var urlRegex = /\b(https?:\/\/[^\s+\"\<\>]+)/ig;
var snapTextElements = document.evaluate("//text()[not(ancestor::a) " +
"and not(ancestor::script) and not(ancestor::style) and " +
"contains(translate(., 'HTTP', 'http'), 'http')]",
document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i = snapTextElements.snapshotLength - 1; i >= 0; i--) {
var elmText = snapTextElements.snapshotItem(i);
if (urlRegex.test(elmText.nodeValue)) {
var elmSpan = document.createElement("span");
var sURLText = elmText.nodeValue;
elmText.parentNode.replaceChild(elmSpan, elmText);
urlRegex.lastIndex = 0;
for (var match = null, lastLastIndex = 0;
(match = urlRegex.exec(sURLText)); ) {
elmSpan.appendChild(document.createTextNode(
sURLText.substring(lastLastIndex, match.index)));
var elmLink = document.createElement("a");
elmLink.setAttribute("href", match[0]);
elmLink.appendChild(document.createTextNode(match[0]));
elmSpan.appendChild(elmLink);
lastLastIndex = urlRegex.lastIndex;
}
elmSpan.appendChild(document.createTextNode(
sURLText.substring(lastLastIndex)));
elmSpan.normalize();
}
}