BYOB: Build Your Own Browser, Part 2by Andrew Anderson
Editor's note: Back in a distant time and galaxy, we ran a nifty article titled Build Your Own Browser where Andrew Anderson showed you the basics of WebKit. It was an introduction to a multipart series (some of the TalkBacks in that first article bemoaned its simplicity, wanting to get to the more advanced stuff). Then, to add fuel to the conversation, Andrew got hit with a big project and the second installment was delayed. Now I have both Parts 2 and 3 in hand. Today's installment picks up where we left off previously, plus includes a discussion about Safari and WebKit security. Next Friday I'll run Part 3, where Andrew shows you how to create a preference window and content eliminator.
The wait is over and the second installment of "Build Your Own Browser" (BYOB) is finally here. The first installment focused on using Interface Builder to build a simple browser without using any code. The browser was functional but lacked many of the features that are available in modern web browsers. The next two installments will make our browser more powerful by using some of the more advanced aspects of WebKit to integrate three new features into our browser:
- Multi-window capabilities
- Preference window
- Content eliminator
This week's installment will cover multi-window capabilities. Both installments differ from the previous article in one significant way. While they do run through tutorials for all three new features similarly to the first article, each leaves hints and tips on how to add additional features, instead of explicitly defining them. This is both to keep the series to a readable length and also to allow readers the ability to develop their own unique features. I've also included some information on WebKit security issues and other sites, which might be of interest for browser developers. I'm going to start with that because it's topical at the moment, then get back to adding features to our fledgling browser.
Safari and WebKit Security
Ask any users of Mac OS X and they will say that "OS X is way more secure than Windows," and then they will tell you that they've never had a virus, Trojan horse, or other security problem with their OS X machine. While OS X is more secure than Windows (Richard Forno has an in-depth article on it ), it is naive to assume that "more secure" is the same as secure, or even worse "more secure" is sufficient to protect your computer. In today's networked world, where your Mac OS X machine is connected 24/7 to the Internet, security is the exception, not the rule.
Our simple browser is more secure than most browsers because it does not implement a lot of functionality that can break. As we add more features to our browser though, we need to keep track of not only our own code, but also any security issues in WebKit. Sounds easy enough, right? Nope, the problem is that users, not developers, find most security problems and users don't know what WebKit is. Since Safari is based on WebKit, the easiest way to keep track of WebKit security issues is to stay informed of Safari security advisories.
Safari security advisories in the past have been few and far between, but a few have surfaced lately, including a recent serious issue involving Safari's interaction with Apple's Help system. It ends up that, given the correct parameters, you can get Safari to open Help and then Help will open any specified application. It is easy to think that this bug is a problem with Safari and Help, but really this affects any browser setup that supports AppleScript and the Help system, including anything based on WebKit.
In its current state, our browser does not suffer from this problem because we have not added support for Help (via its MIME type and/or the Download delegate), but if you plan on expanding the browser, you need to watch out for issues like this one. Fortunately, once Apple caught wind of this bug, it fixed it quickly (although there is some debate on how quickly), and the fix is available in the latest security updates.
For more information on the bug and a simple script that exploits it, check out the overview at François Nonnenmacher's site.
OK, now with that bit of business out of the way, let's get back to enhancing our basic browser.
Our application is pretty limited at this point; basically, it's a single window in a single frame. And while it can load complex pages and lay them out correctly, it leaves plenty to be desired in terms of functionality. One important piece of functionality that's missing is the ability to open multiple windows at the same time.
In order to achieve this feat, our browser is going to have to be a Cocoa Document-based application. Currently our project is a simple Cocoa Application, which means that we will have to convert our old project into a Cocoa Document-based application. The easiest and most elegant way to do this is to create a new project that is Document-based and copy the relevant parts of the old project to it.
Start by launching Xcode and creating a new "Cocoa Document-based application" project. When Xcode is finished creating the project, the next step is to add the WebKit framework. To do this, go to the Other Frameworks folder under Frameworks folder in the main project section of the new application. Go to the Projects menu bar and choose Add Framework. In the dialog that pops up, find the WebKit framework and add it to the project. The WebKit framework generally resides in "/System/Library/Frameworks/WebKit" and is surprisingly named "WebKit.framework". If you can't find it, check out the original article for instructions on downloading and installing the framework.
Changing the UI
Now it's time to add the original user interface from the old project to the
new project. Since our original project was not a document-based application,
the user interface was built in
MainMenu.nib. In the new project,
the user interface for the browser functionality will be built in
MyDocument.nib is the file that Cocoa uses to create a new user
interface each time a request is made for one. To get the new browser to function
like the previous version we need to copy the UI elements in
Copying the UI elements between
is fairly easy, load both files in Interface Builder and copy the contents from
MyDocument.nib. The easiest way to
do this is to load both the original and the new project in XCode. Once they
are both loaded, double click
MainMenu.nib from the original project
MyDocument.nib from the new project, which will load both
NIB files in Interface Builder. Once in Interface Builder, go to
select all the UI elements, copy them, then switch windows to
and paste the new elements into the window. The last step is to save
After the UI is built, we need to add connections so the code that we'll develop later will be able to access the UI pieces. When dealing with Cocoa Document-based applications, the connections are made between the "MyDocument" window instance and the "File's Owner" instance. "File's Owner" is a proxy for the "MyDocument" object that corresponds to individual instances of the class. The need to make a total of three connections between "File's Owner" and "MyDocument": two outlets, one for the webView and one for the URL text line; and one action from the URL text line to alter the document for when the value is changed.
To start, we need to choose the MyDocument class in the Classes panel of the Interface Builder control panel (it is a subclass of NSObject). Once MyDocument is chosen, go to the "Classes" pulldown menu and choose "Add Outlet to MyDocument." An information window will pop up; make sure that it is on the "Outlets" tab and add two outlets: "webView" with type "WebView" and "urlString" with type "id." Next switch to the actions tab and add an action named "connectURL."
Now that the outlets and actions are set up, it's time to connect them to the
"File's Owner" instance. To connect the outlets, go to the "Instances" tab of
MyDocuments.nib, choose the "File's Owner" instance and Ctrl-drag
a line to the text field on the user interface. The info window will appear
on the connections page, where you should select "urlString" under the "Outlets"
tab and click connect. Next, do the same thing to connect the WebView instance
to the webView outlet.
The last step is to disconnect the WebView's
takeStringURLFrom action from
the edit field and reconnect the edit field to the connectURL action that we
created before in "File's Owner". To disconnect from
the text input field and Ctrl-drag a line to the WebView on "MyDocument"; when
information window pops up, choose the "Target/Actions" tab, then click on "takeStringFromURL"
and click on the disconnect button. To reconnect the text input field to the "File's
Owner" instance, Ctrl-drag a line from the text input field to the "File's Owner"
instance, choose the "Targets/Actions" tab, and click on connect.
Once that is all hooked up, save the NIB file and quit Interface Builder. It's time to edit some code!