The WebView: Reloaded

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 loadRequest:. Vienna does pass an 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.

4 Responses to “The WebView: Reloaded”

  1. Helge says:

    Sauerkraut: nominative singular, neutrum – correct article: das
    Lederhosen: accusative plural, female – correct possessive pronoun: meinen

    Das Sauerkraut ist in meinen Lederhosen.

  2. Jeff says:

    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:

    
    void FrameLoader::reload()
    {
        if (!m_documentLoader)
            return;
    
        NSMutableURLRequest *initialRequest = m_documentLoader->request();
    
  3. Bret says:

    Did ya’ file a bug with the workaround?

  4. Jeff says:

    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!