Archive for November, 2006

WordPress Bug Friday, nth edition

Friday, November 24th, 2006

Yesterday was Thanksgiving in the United States. Thanksgiving is a day for giving thanks, obviously. More important, it means that we Americans get to be ungrateful boors every other day of the year. Don’t you love holidays? Now that I don’t have to be thankful again (or eat again, for that matter) until next November, I’d like to take this opportunity to rip on WordPress. For the most part, WordPress works admirably. However, as I’ve noted once or twice, it doesn’t play well with syndication, which as you may know is something I’m familiar with. A number of you have WordPress installations on your own web sites, and even those of you who don’t have WordPress installations probably experience these bugs by following the syndicated feeds of this site and others in your favorite feed reader (Vienna, perhaps).

Today’s bug, which still exists in WordPress 2.0.5 (but has been fixed by me on this web site), afflicts comments feeds : for example, the main comments feed for the blog as well as comments feeds for individual posts. In order to prevent spam — as well as egg, bacon, spam, and sausage — I moderate all comments. No matter how long it takes for me to approve a comment (not long, usually, unless I’m asleep: that’s when I’m a Viking), the comment will appear with the date and time it was written. That’s fine, except that if your feed reader is polite like Vienna and sends an If-Modified-Since header in the request for articles, WordPress looks at the date of the latest comment in the database, regardless of whether the comment has been approved. Thus, if your feed reader checks the comments feed after a comment has been written but before it has been approved, the feed will give an HTTP 200 response (“OK”, “Yea!”, “Come hither”) and send the feed data, though thankfully WordPress won’t actually send the unapproved comment. Until another comment is written, however, the feed will give an HTTP 304 response (“Not Modified”, “Can’t Touch This!”, “I have a headache tonight”) on all subsequent checks, so you won’t see the comment in the feed after it’s approved.

By the way, if you wonder why I read the Hypertext Transfer Protocol document, take a glance at section 13.1.4. It’s good stuff, folks! I have yet to see a browser that follows the suggestions given by the RFC. Core Animation should make it easy.

Anyway, to fix the bug in WordPress 2.0.5, you need to modify the function get_lastcommentmodified in the file wp-includes/comment-functions.php. Replace the switch statement with the code below. You’re just adding $wpdb->comments.comment_approved = '1' AND to each of the cases.


switch(strtolower($timezone)) {
    case 'gmt':
        $lastcommentmodified = $wpdb->get_var("SELECT comment_date_gmt FROM $wpdb->comments WHERE $wpdb->comments.comment_approved = '1' AND comment_date_gmt <= '$now' ORDER BY comment_date_gmt DESC LIMIT 1");
        break;
    case 'blog':
        $lastcommentmodified = $wpdb->get_var("SELECT comment_date FROM $wpdb->comments WHERE $wpdb->comments.comment_approved = '1' AND comment_date_gmt <= '$now' ORDER BY comment_date_gmt DESC LIMIT 1");
        break;
    case 'server':
        $lastcommentmodified = $wpdb->get_var("SELECT DATE_ADD(comment_date_gmt, INTERVAL '$add_seconds_server' SECOND) FROM $wpdb->comments WHERE $wpdb->comments.comment_approved = '1' AND comment_date_gmt <= '$now' ORDER BY comment_date_gmt DESC LIMIT 1");
        break;
}

Alas, WordPress Bug Friday is already over again, too soon! Remember that there are now only 30 shopping days left until Christmas. You can get me anything from the store.

The WebView: Reloaded

Sunday, November 19th, 2006

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.

Vienna 2.1 and WordPress 2.0.5

Friday, November 10th, 2006

Ok, I lied. I won’t be talking about Cocoa programming in this post, except insofar as Vienna is a Cocoa program. This is really more of an announcement than a discussion, though. The Emergency Broadcast System has instructed me to annoy you with a series of loud beeps. If this had been an actual emergency, I would have been looting your valuables right now.

Beep! Beep! Beep! People of Earth, your attention, please. The developers of Vienna formally announce the release of … Vienna. Specifically, Vienna 2.1, the exciting sequel to Vienna 2.0. In a surprising turn of events, the incumbent, build 2107, has been upset by the challenger, build 2108. Sadly, build 2107 refuses to concede and is demanding a recount. Meanwhile, build 2109 is already setting up an exploratory committee for the next release. The release notes for Vienna 2.1 can be found in the forum. For those who prefer pictures over text, check out the illustrated list of features.

I would like to thank Steve Palmer for letting me mess with and mess up his app. Hopefully I’ve fixed more things than I’ve broken. I’d like to thank the Academy (Plato’s). It’s an honor just to nominate myself. Thanks to my cats, for helping me type. Thanks to caffeine, in all its forms. Thanks to my user agent, “Vienna/2.1.0.2108″. Finally, I’d like to thank all the little people: Dr. Ruth, Dustin Hoffman, Kerri Strug, Prince, and Ronnie James Dio. (Cue the band.)

My other important announcement is that the blog may be unavailable for a minute or two while I update from WordPress 2.0.4 to 2.0.5. By the time you read this post, it’ll all be over. That wasn’t so bad, was it? Just a little pinprick. Ahhhhhh! If you notice any problems after the update, please contact me. This is a recording. So long, and thanks for all the fish.

CSS tips for HTML

Thursday, November 9th, 2006

I apologize for the absence of posts recently. I’m not dead! I just thought I’d go for a walk. I was feeling the pressure to be funny constantly, so I’ve contracted with Bobcat Goldthwait to ghostwrite my blog posts for me. Umm, I-I-Arrgh, I want a yacht!

During the hiatus, I’ve also been redesigning my web site. Superficially it looks similar, but under the hood it’s now almost entirely hand-coded (with Smultron). The only iWeb-generated code I kept was the JavaScript photo slide show. The benefits of the redesign are that the pages should load faster, and it’ll be easier for me to update the content, as I just did by adding a link to Mac OS Forge, which has finally emerged from its beauty sleep.

In honor of our new site styles, the subject of this post will be CSS. Sorry, folks, one cannot live on Cocoa alone! One also requires marshmallows, and Fritos. You may have heard of collapsing margins, which occurs in a number of contexts, for example, on election day. It also occurs when the bottom margin of a block-level element on a web page adjoins the top margin of another block-level element. The two margins collapse, and the length of the displayed margin becomes the length of the larger of the two adjoining margins. This can be useful, because typically you want a minimum margin around a paragraph of text, and you want a different minimum margin around a heading, but you don’t want a huge gap between the heading and the first paragraph. (Unless you’re trying to turn 8 pages of actual writing into a 15-page term paper. I know who you are, slackers! I was a teacher. Your grade will reflect your ef-fort.)

What you may not realize is that collapsing margins also occurs when the top margin of a block adjoins the top margin of its parent block. If you load the following HTML in a browser, such as Safari or Camino, you’ll see a purple box at the top of the window.


<html lang="en-US">
<head>
<title>Collapsing Margins</title>
<style type="text/css">
body {
	background-color: #000000;
	margin: 0;
	color: #cccccc;
}
#all_content {
	background-color: #590079;
	margin-top: 0;
	margin-bottom: 0;
	margin-left: auto;
	margin-right: auto;
	width: 40em;
	height: 40em;
}
p {
	margin-top: 1em;
	margin-bottom: 1em;
}
</style>
</head>
<body>
<div id="all_content">
</div>
</body>
</html>

This is to be expected, because body and all_content both have top margins of 0. However, if you add a paragraph, <p>This is a paragraph.<p>, inside the element all_content, something unexpected happens. Instead of the purple box at the top of the window with the paragraph spaced from the top of the box, you see the purple box spaced from the top of the window with the paragraph at the very top of the box.

The margins of the paragraph are supposed to be calculated relative to its containing block, which in this case is the element all_content, so you would expect that the margin should appear between the paragraph and the box rather than between the box and the top of the window. The reason the opposite occurs is that even though the box has a top margin of 0, the top margin of the box and the top margin of the paragraph are technically adjoining, which means the margins collapse, and the one true margin becomes the larger of the two top margins. Since margins are transparent, the black background shows through.

The CSS specifications for collapsing margins are, to put it frankly, idiotic. Or to put it more tactfully, they’re insane. Who in the world wide web would want the top margins of parent and child blocks to collapse? I gave the paragraph a non-zero top margin because, well, I wanted it to have a non-zero top margin! I don’t blame the browser developers for complying to the standards; web designers demand and expect the browsers to be standards-compliant. Rather, I blame the standards developers for developing substandard standards. I can’t imagine what they were thinking. Anyway, the workaround for this bug, as I shall call it, is to give the parent block, in this case all_content, a non-zero border-top or padding-top. If the parent has a border or padding, then the top margin of the child does not adjoin the top margin of the parent, the margins do not collapse, and all is right with the world again.

Speaking of standards-compliance, I used to consider Mozilla’s Gecko, the rendering engine behind Camino and Firefox, the standards king. Perhaps it was, in the early days of Safari. I believe that Gecko has lagged behind while WebKit, the rendering engine underlying Safari and Vienna, among others, has caught up and surpassed it. While testing my web site in Camino, I ran into a couple of problems. First, Gecko doesn’t handle the inline-block display property. This bug has been open since 1999. I would advise web designers not to use inline-block if they want their pages to display properly in Gecko browsers.

Second, there is a bug, open since 2004, in how Gecko calculates percentage margins for a float with width: auto. You can see this by comparing how my résumé looks in Safari and Camino, under the “Degrees” heading, for example. (Is this a ploy to get you to read my résumé? No, just a happy coincidence.) Percentage margins are supposed to be calculated relative to the size of the containing block, but Gecko calculates them relative to the size of the float, so that the width of the margin is a percentage of the sum of the widths of the float and margin. This makes the margin far too small. My workaround, as you can see in the HTML source, was to increase the margin’s percentage size somewhat. It still looks too small in Camino, but I don’t want to make it too large in Safari. Although I could have used a table in this situation rather than floats, it made much more sense semantically to arrange the content by columns rather than by rows.

I hope that you enjoyed my rants, err, tips. I promise to talk about Cocoa programming in the next post. I also promise to lower taxes, raise spending, balance the budget, ask not what my country can do for me, fear nothing but fear itself (that is, the form of fear, not those shadowy particular fears), put a chicken in every pot, a car in every garage, and a kitten in every lap. Of course, this all depends on what the meaning of the word “I” is. I want a yacht. Thank you very much. Argh.

Modifier keys for opening external links

Thursday, November 2nd, 2006

This is the first of a series of posts (two) on the method -[NSWorkspace openURLs: withAppBundleIdentifier: options: additionalEventParamDescriptor: launchIdentifiers:], described in the NSWorkspace Class Reference. The first post will consist entirely of naming the method, because just typing it has given me carpal tunnel syndrome.

My cat graciously offered to type, so we’ll press ahead. Vienna has a built-in WebKit browser that allows you to follow links in a feed without switching to another app. There is a preference to choose whether to open links in Vienna or in your default web browser (in my case, Safari). Opening an array of URLs in your default web browser can be accomplished by calling the above mentioned method with the parameter withAppBundleIdentifier: set to nil.

Occasionally I want to override Vienna’s link opening preference. For example, I might want to bookmark some pages in Safari while still doing most of my web browsing in Vienna. (Note to self: implement feature in Vienna to add page to bookmarks.) My very first contribution to the Vienna project was a patch to override the preferred link opening behavior with a keyboard shortcut. I used the shift key, because at that point the option key was reserved for another purpose. The patch would do the opposite of your preference when you shift-clicked a link in the article pane or typed shift-return in the articles list. In other words, it would give you chicken salad, on rye, untoasted, and a cup of tea.

Using the shift key for this feature was a mistake, I later discovered. I received a bug report in the forum from a user who had minimized a Safari window to the Dock. When the user pressed the shift key in Vienna to open a link, the page did indeed open in Safari … extremely … slowly. My new Vienna feature had inadvertently activated the Mac OS X super-slow-motion effect for opening minimized windows. (The only purpose of this effect, as far as I can tell, is to mildly impress Windows users.) I faced another apparent example of relativistic weirdness, as if the shift key created time dilation by accelerating the rest of the system to near light speed, leaving Safari to lumber along like a gorilla. You maniacs! You blew it up! Super slowly!

To make a long story even longer, I fixed the bug in Vienna by switching the modifier key from shift to option. All recent builds of Vienna use the option key to override the browsing preference. My advice to you is to avoid using the shift key in your application as a modifier to open external links. Thank you very little.