I know, I’ve been neglecting my blog again. My excuse is that I’ve been working on a secret project, so secret that if I told you, I’d have to buy you a pitcher of margaritas and hope you forgot. It’s not quite Top Secret: more like Middle Secret. (No, it wasn’t TomKat’s wedding.)
As promised, I’ll talk about Cocoa programming, specifically, WebKit programming. (Technically, WebKit is not Cocoa — at least, it’s not included by Cocoa.h — but you can use it in your Cocoa projects, sopleasedonotsueme!) As usual, the story begins with a bug report from a Vienna user. I swear, Vienna is no more buggy than your average app, or ant hill. At any rate, I don’t think that this bug was really our fault. The user reported that after a lost internet connection was restored, Vienna’s web browser couldn’t reload a page that had initially failed to load. However, Vienna’s reload button just uses the WebKit action -[WebView reload:], and Safari, which also uses WebKit, can reload a page once the internet connection is restored.
The class reference for WebView says that its reload: method Reloads the current page.
That’s helpful. Luckily, WebKit is open source. In the file WebView.m, we find the implementation for the action:
- (IBAction)reload:(id)sender
{
[[self mainFrame] reload];
}
Ok, that makes sense. Yet the class reference for WebFrame, which is slightly more helpful, says that its reload method Reloads the initial request passed as an argument to
Vienna does pass an loadRequest:.NSURLRequest as an argument when calling loadRequest: on the mainFrame of the WebView. Why doesn’t the page get reloaded? Returning to the source, the file WebFrame.m contains the implementation:
- (void)reload
{
[_private->frameLoader reload];
}
In WebFrame.h, we see that _private is an instance variable:
@interface WebFrame : NSObject
{
@private
WebFramePrivate *_private;
}
Back in WebFrame.h, we can see that frameLoader is, in turn, an ivar of the class WebFramePrivate:
@interface WebFramePrivate : NSObject
{
@public
WebFrameView *webFrameView;
WebFrameLoader *frameLoader;
Finally, in WebFrameLoader.m we see the problem:
- (void)reload
{
WebDataSource *ds = [self dataSource];
if (ds == nil)
return;
NSMutableURLRequest *initialRequest = [ds request];
The WebFrame does not keep a reference to the NSURLRequest! The initial request comes from the data source, but the documentation for -[WebFrame loadRequest:] indicates that that there is no (committed) data source until data has been received. Obviously, no data has been received if there was no internet connection. When there is no data source, reloading the page does, well, nothing. In other words, the description of -[WebFrame reload] should be amended with, Now, where did I put that initial request? I thought I had it around here somewhere. Check under the bed.
The solution to this problem is to keep your own reference to the NSURLRequest and override -[WebView reload:], as follows.
-(IBAction)reload:(id)sender {
if ([[self mainFrame] dataSource] == nil) {
[[self mainFrame] loadRequest:myRequest];
} else {
[super reload:sender];
}
}
I’ve created a demonstration project that contrasts this with the default reload: method. To see how they work (or don’t, as the case may be), build and run the application, turn off your net connection, press the Load button, turn your connection back on, and then experiment with the Reload buttons. You can see in the run log that both dataSource and provisionalDataSource are nil.
Die Sauerkraut ist in mein Lederhosen.
Sauerkraut: nominative singular, neutrum – correct article: das
Lederhosen: accusative plural, female – correct possessive pronoun: meinen
Das Sauerkraut ist in meinen Lederhosen.
Update: I think the code above comes from the Mac OS X 10.4.7 release version of WebKit. I just updated to the 10.4.8 release version, and they’ve switched a number of implementation files from Objective-C to C++ for some reason. I find that rather upsetting, actually. Does anyone know why? Regardless, it appears that reload works the same way as before. From the file
FrameLoader.mm:Did ya’ file a bug with the workaround?
I probably should file a bug, but I haven’t. I could imagine it becoming an argument about the semantics of “reload”. Anyway, I’d rather that they spend time fixing more important bugs, like crashers!