How much web browser can you put in 128 lines of code?
UPDATE: If you read this and all you can say is "oh, he's just embedding WebKit", I have two things to tell you:
Duh! Of course the 128 lines don't include the rendering engine, or the TCP implementation, or the GUI toolkit. This is about the rest of the browser, the part around the web rendering engine. You know, just like Arora, Rekonq, Epiphany, and everyone else that embeds webkit or mozilla does it? If you didn't get that before this explanation... facepalm.
Get your favourite webkit fork and try to do this much with the same amount of code. I dare you! I double dog dare you!
Now back to the original article
Today, because of a IRC chat, I tried to find a 42-line web browser I had written a while ago. Sadly, the pastebin where I posted it was dead, so I learned a lesson: It's not a good idea to trust a pastebin as code repository
What I liked about that 42-line browser was that it was not the typical example, where someone dumps a Webkit view in a window, loads a page and tries to convince you he's cool. That one is only 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 wanted to make the code uglier, it could be done in 6.
But anyway, that 42-line browser actually looked useful!
Those buttons you see actually worked correctly, enabling and disabling at the right moment, the URL entry changed when you clicked on links, and some other bits.
So, I have decided to start a small, intermittent project of code golf: put as much browser as I can in 128 lines of code (not counting comments or blanks), starting with PyQt4.
This has a useful purpose: I always suspected that if you assumed PyQt was part of the base system, most apps would fit in floppies again. This one fits on a 1.44MB floppy some 500 times (so you could use 360KB commodore floppies if you prefer!).
So far, I am at about 50 lines, and it has the following features:
Zoom in (Ctrl++)
Zoom out (Ctrl+-)
Reset Zoom (Ctrl+=)
Find (Ctrl+F)
Hide find (Esc)
Buttons for back/forward and reload
URL entry that matches the page + autocomplete from history + smart entry (adds http://, that kind of thing)
Plugins support (including flash)
The window title shows the page title (without browser advertising ;-)
Progress bar for page loading
Statusbar that shows hovered links URL
Takes a URL on the command line, or opens http://python.org
Multiplatform (works in any place QtWebKit works)
Missing are tabs and proxy support. I expect those will take another 40 lines or so, but I think it's probably the most featureful of these toy browsers.
The code... it's not all that hard. I am using lambda a lot, and I am using PyQt's keyword arguments for signal connection which makes lines long, but not hard. It could be made much smaller!
Here it is in action:
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_())
You sir, are a scholar and a gentleman.
Protip: use Github gists if you want any sense of permanence in your pastebin.
Fantastic, cool example. On a side note, do you have any tips for debubbing Javascript with WebKit
Thanks, no I don't :-(
At least not beyond showing the inspector and working from there?
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.
That's very interesting!
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.
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.
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.
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!
That's just fine for me! The code is under a MIT license. Ping me if you need help with something :-)
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...
BTW: latest code is at http://devicenzo.googlecode... (since I don't seem to have linked it on this post)