Show All Feeds

An Eccentric Anomaly: Ed Davies's Blog

I read about one hundred blogs on a regular basis. Luckily, not all of them post every day. Still, most are worth checking for new content fairly regularly which seems like the sort of things computers ought to be able to do.

Of course they can. Sites which publish blogs and blog-like content (e.g., podcasts) usually have a feed which is an XML document with references to new and recently changed pages. Something's not a proper blog if it doesn't have a feed. There are two main flavours of feed, RSS and Atom, and RSS comes in various sub-flavours so it's all a bit chaotic but for practical use most readers can read all the flavours so they're largely interchangeable.

Personally, I use Liferea (Linux Feed Reader) but there are quite a few other readers around with various strengths and weaknesses.

Feeds are not as widely used as one would expect. E.g, there are a lot of people who rely on email subscriptions to blogs. It's not obvious why this is: I suspect that it's a combination of the whole thing of setting up a feed reader and adding feeds to it being something the user has to initiate themselves whereas they can be lead to other sorts of subscription by clicking links plus once things are set up it's still a little fiddly to find the links from an interesting blog to its associated feed, copy that link, create a new subscription in the feed reader and paste the link into it.

As feeds aren't used that much browser support has been rather patchy which has contributed to feeds not being used much, and round we go. A long time ago you just had to rely on blogs advertising their feed URLs directly in the user interface (as I do, top right, below the “Contact” link) but then link elements where added in HTML to allow automatic discovery of the feeds, as well.

Firefox (and I assume other browsers) showed those links on the address bar allowing relatively easy discovery and use in feed readers. But it was still a bit fiddly so Mozilla added their own feed reader built into Firefox (which made things more awkward for users of other feed readers, I often reverted to use of “view source” to find feed link elements). Then they decided that not enough people were using their feed reader so they dropped feed support altogether.

So people dragged out the little bits of JavaScript they'd been using years ago to find the link elements. For one, Alex Schroeder published this Show All Feeds bookmarklet.

That's nice but the code is rather quaint. Rehydrated by adding some whitespace: show-feeds.original.js.

To my mind, this code has two obvious flaws:

It was the relative-URL handling I noticed first. E.g., the link elements for this blog are like:

<link href="/blog/atom.xml" rel="alternate" title="Atom feed"
type="application/atom+xml"/>

where the /blog/atom.xml href is relative to the site. The code as published resolves this to http://edavies.me.uk//blog/atom.xml when it should be https://edavies.me.uk/blog/atom.xml so with the wrong scheme and an extra '/' in the middle. This will actually work in this case but will involve an extra round trip to the server to redirect to the right scheme.

As Rachel by the Bay says: “Strings are hard!”. Actually, it's more that blobs of structured data masquerading as simple strings are often a lot harder than they look. This is true of IP addresses, URLs, XML and a lot else. In general it's better to use specialised code which has been written by somebody who's thought about the matter a bit to do the manipulation.

Luckily, modern browsers have a function (a constructor, actually) to do exactly what we want here: compose a URL from an existing, possibly-relative, URL and a base URL. It's called, prosaically enough: URL so we can replace:

        if(!href.match(/^http/)){
            var path=(href.match(/^\//))? '/' : location.pathname;
            href='http://'+location.hostname+path+href;
        }

with:

        href = (new URL(href, location)).href;

That should now work for all URL schemes, including gopher: ;-).

The second flaw of this BC (Before Crockford) code is its use of variables and functions in the global (window) namespace. It's not unlikely that some of the names it defines will clash with ones used by other JavaScript code in the same window. It even clashes with itself: if you invoke the bookmarklet twice for any reason (e.g., you're comparing my version with the original) then the el variable from the first gets overwritten by the second resulting in the Close button on the first no longer working.

The solution is simple: wrap the whole lot in an anonymous immediately invoked function. Each invokation will be a separate closure with its own namespace quite separate from the JavaScript global namespace. This closure won't be garbage collected with the onclick event of the close button being left connected but that's not going to matter in reality.

The result is: show-feeds.js.

This can be squished down to produce the following bookmarklet:

javascript:(function(){function%20txt(str){return%20document.createTextNode(str)}function%20tag(n,c){var%20e=document.createElement(n);e.style.fontFamily='Arial,sans-serif';e.style.color='#000';if(c)e.appendChild(c);return%20e}function%20p(c){return%20tag('p',c)}function%20a(href,desc){e=tag('a',txt(desc));e.href=href;e.style.color='#00c';e.style.textDecoration='underline';return%20e}var%20el=tag('div');el.style.zIndex=100000;el.style.position='absolute';el.style.padding='20px';el.style.top='10px';el.style.left='10px';el.style.backgroundColor='#ffffcc';el.style.border='1px%20solid%20#333333';el.style.textAlign='left';var%20ul=tag('ul');var%20found=false;var%20links=document.getElementsByTagName('link');for(var%20i=0,link;link=links[i];i++){var%20type=link.getAttribute('type');var%20rel=link.getAttribute('rel');if(type&&(type=='application/rss+xml'||type=='application/atom+xml')&&rel&&rel=='alternate'){var%20href=link.getAttribute('href');href%20=%20(new%20URL(href,%20location)).href;var%20title=link.getAttribute('title');ul.appendChild(tag('li',a(href,((title)%20?%20title+'%20-%20'%20:%20'')+href)));found=true;}}if(found){el.appendChild(p(txt('The%20current%20page%20links%20to%20these%20feeds:')));el.appendChild(ul);}else{el.appendChild(p(txt('The%20current%20page%20does%20not%20link%20to%20any%20feeds.')));}var%20close=a('#','Close');close.onclick=function(){el.style.display='none';return%20false;};el.appendChild(p(close));function%20addFeedBox(){document.body.appendChild(el);y=window.scroll(0,0);}void(z=addFeedBox());})();

Works for me in Firefox and Chromium.