Skip to main content

Ralsina.Me — Roberto Alsina's website

How much web browser can you put in 128 lines of code?

UP­DATE: If you read this and all you can say is "o­h, he's just em­bed­ding We­bKit", I have two things to tell you:

  1. Duh! Of course the 128 lines don't in­­­clude the ren­der­ing en­gine, or the TCP im­­ple­­men­­ta­­tion, or the GUI tool­k­it. This is about the rest of the browser, the part around the web ren­der­ing en­gine. You know, just like Aro­ra, Rekon­q, Epiphany, and ev­ery­one else that em­beds we­bkit or mozil­la does it? If you did­n't get that be­­fore this ex­­pla­­na­­tion... facepalm.

  2. Get your favourite we­bkit fork and try to do this much with the same amount of code. I dare you! I dou­ble dog dare you!

Now back to the orig­i­nal ar­ti­cle


To­day, be­cause of a IRC chat, I tried to find a 42-­line web brows­er I had writ­ten a while ago. Sad­ly, the paste­bin where I post­ed it was dead, so I learned a lesson: It's not a good idea to trust a paste­bin as code repos­i­to­ry

What I liked about that 42-­line brows­er was that it was not the typ­i­cal ex­am­ple, where some­one dumps a We­bkit view in a win­dow, loads a page and tries to con­vince you he's cool. That one is on­ly 7 lines of code:

import sys
from PyQt4 import QtGui,QtCore,QtWebKit
app=QtGui.QApplication(sys.argv)
wb=QtWebKit.QWebView()
wb.setUrl(QtCore.QUrl('http://www.python.org'))
wb.show()
sys.exit(app.exec_())

And if I want­ed to make the code uglier, it could be done in 6.

But any­way, that 42-­line brows­er ac­tu­al­ly looked use­ful!

This 42-line web browser, courtesy of #python and #qt -- http... on Twitpic

Those but­tons you see ac­tu­al­ly worked cor­rect­ly, en­abling and dis­abling at the right mo­men­t, the URL en­try changed when you clicked on links, and some oth­er bit­s.

So, I have de­cid­ed to start a smal­l, in­ter­mit­tent project of code golf: put as much brows­er as I can in 128 lines of code (not count­ing com­ments or blanks), start­ing with PyQt4.

This has a use­ful pur­pose: I al­ways sus­pect­ed that if you as­sumed PyQt was part of the base sys­tem, most apps would fit in flop­pies again. This one fits on a 1.44MB flop­py some 500 times (so you could use 360KB com­modore flop­pies if you prefer­!).

So far, I am at about 50 lines, and it has the fol­low­ing fea­tures:

  • Zoom in (C­tr­l++)

  • Zoom out (C­tr­l+-)

  • Re­set Zoom (C­tr­l+=)

  • Find (C­tr­l+F)

  • Hide find (Esc)

  • But­­tons for back­­/­­for­ward and reload

  • URL en­try that match­es the page + au­­to­­com­­plete from his­­to­ry + smart en­try (adds http://, that kind of thing)

  • Plug­ins sup­­port (in­­clud­ing flash)

  • The win­­dow ti­­tle shows the page ti­­tle (with­­out brows­er ad­ver­tis­ing ;-)

  • Progress bar for page load­­ing

  • Sta­­tus­bar that shows hov­­ered links URL

  • Takes a URL on the com­­mand line, or opens http://python.org

  • Mul­ti­­plat­­form (works in any place QtWe­bKit work­s)

Miss­ing are tabs and proxy sup­port. I ex­pect those will take an­oth­er 40 lines or so, but I think it's prob­a­bly the most fea­ture­ful of these toy browser­s.

The code... it's not all that hard. I am us­ing lamb­da a lot, and I am us­ing PyQt's key­word ar­gu­ments for sig­nal con­nec­tion which makes lines long, but not hard. It could be made much small­er!

Here it is in ac­tion:

And here's the code:

#!/usr/bin/env python
"A web browser that will never exceed 128 lines of code. (not counting blanks)"

import sys
from PyQt4 import QtGui,QtCore,QtWebKit

class MainWindow(QtGui.QMainWindow):
    def __init__(self, url):
        QtGui.QMainWindow.__init__(self)
        self.sb=self.statusBar()

        self.pbar = QtGui.QProgressBar()
        self.pbar.setMaximumWidth(120)
        self.wb=QtWebKit.QWebView(loadProgress = self.pbar.setValue, loadFinished = self.pbar.hide, loadStarted = self.pbar.show, titleChanged = self.setWindowTitle)
        self.setCentralWidget(self.wb)

        self.tb=self.addToolBar("Main Toolbar")
        for a in (QtWebKit.QWebPage.Back, QtWebKit.QWebPage.Forward, QtWebKit.QWebPage.Reload):
            self.tb.addAction(self.wb.pageAction(a))

        self.url = QtGui.QLineEdit(returnPressed = lambda:self.wb.setUrl(QtCore.QUrl.fromUserInput(self.url.text())))
        self.tb.addWidget(self.url)

        self.wb.urlChanged.connect(lambda u: self.url.setText(u.toString()))
        self.wb.urlChanged.connect(lambda: self.url.setCompleter(QtGui.QCompleter(QtCore.QStringList([QtCore.QString(i.url().toString()) for i in self.wb.history().items()]), caseSensitivity = QtCore.Qt.CaseInsensitive)))

        self.wb.statusBarMessage.connect(self.sb.showMessage)
        self.wb.page().linkHovered.connect(lambda l: self.sb.showMessage(l, 3000))

        self.search = QtGui.QLineEdit(returnPressed = lambda: self.wb.findText(self.search.text()))
        self.search.hide()
        self.showSearch = QtGui.QShortcut("Ctrl+F", self, activated = lambda: (self.search.show() , self.search.setFocus()))
        self.hideSearch = QtGui.QShortcut("Esc", self, activated = lambda: (self.search.hide(), self.wb.setFocus()))

        self.quit = QtGui.QShortcut("Ctrl+Q", self, activated = self.close)
        self.zoomIn = QtGui.QShortcut("Ctrl++", self, activated = lambda: self.wb.setZoomFactor(self.wb.zoomFactor()+.2))
        self.zoomOut = QtGui.QShortcut("Ctrl+-", self, activated = lambda: self.wb.setZoomFactor(self.wb.zoomFactor()-.2))
        self.zoomOne = QtGui.QShortcut("Ctrl+=", self, activated = lambda: self.wb.setZoomFactor(1))
        self.wb.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, True)

        self.sb.addPermanentWidget(self.search)
        self.sb.addPermanentWidget(self.pbar)
        self.wb.load(url)


if __name__ == "__main__":
    app=QtGui.QApplication(sys.argv)
    if len(sys.argv) > 1:
        url = QtCore.QUrl.fromUserInput(sys.argv[1])
    else:
        url = QtCore.QUrl('http://www.python.org')
    wb=MainWindow(url)
    wb.show()
    sys.exit(app.exec_())
Roland Lovelock / 2011-03-04 08:09:

You sir, are a scholar and a gentleman.

Rafe Kettler / 2011-03-04 08:26:

Protip: use Github gists if you want any sense of permanence in your pastebin.

pete / 2011-03-04 13:44:

Fantastic, cool example. On a side note, do you have any tips for debubbing Javascript with WebKit

Roberto Alsina / 2011-03-06 20:38:

Thanks, no I don't :-(
At least not beyond showing the inspector and working from there?

Mason Larobina / 2011-03-04 17:07:

Here is my attempt using the luakit browser framework: https://gist.github.com/855103 I've gone with a modal approach and have managed to squeeze a few more key bindings in.

Roberto Alsina / 2011-03-06 20:38:

That's very interesting!

Ralph / 2011-03-04 17:11:

I tried this out, and it actually works just fine. Only thing is the browsing experience is a bit slow. At first I thought it was a Python thing...but since PyQt wraps the native Qt framework which is written in C or C++ (not sure) shouldn't the browsing experience be somewhat fast? I realize that it's not going to probably be as fast as the mainstream browsers which have a ton of optimizations but just wondering if you could shed some light.

Roberto Alsina / 2011-03-06 20:38:

In my computer I don't see it as significantly slower than other browsers, but I do have a fast-ish computer.
I believe (but this is really just a vague memory) that the Javascript engine in Qt's webkit is not up to par with other implementations, maybe that is ;art of the problem.

Another thing is that there is no persistent disk cache, so it should feel fairly slower than other browsers for sites you visit frequently.

Patx44 / 2011-07-19 21:00:

http://patx.me/fastpatx  << fastPATX is a web browser I wrote. Its very good. Not quite as small as yours... but it does and looks a tad better.

Vinay Sajip / 2012-01-23 19:45:

This is a very nice minimal browser. I'd like to use it in a Sphinx documentation watcher (which is just a small bit of extra code on top of your work) - as I don't see a license on your code, is it OK for me to use it in this way? I'm planning to release the result under the MIT license. Thanks!

Roberto Alsina / 2012-01-23 20:00:

That's just fine for me! The code is under a MIT license. Ping me if you need help with something :-)

Vinay Sajip / 2012-01-24 22:07:

Thanks, Roberto. I've posted about my application of your work to easing the development flow when working on Sphinx documentation: http://pymolurus.blogspot.c...

Roberto Alsina / 2012-01-23 20:03:

BTW: latest code is at http://devicenzo.googlecode... (since I don't seem to have linked it on this post)