Essays

8 months ago

Saturday, June 13, 2009

Fixing Maximize Bookmarklets in Safari 4

When Apple released the Safari 4 public beta at the end of February, I discovered an annoying bug: the little Javascript bookmarklet that I use to maximize the browser window didn’t work anymore. I find the behavior of Safari’s green zoom button extremely annoying. It doesn’t expand the browser window to fill the screen, but instead only resizes it to fit the content of the current page. This is typically some weird height and width that causes every subsequent page you view scroll vertically and horizontally.

Of course, Apple’s Human Interface Guidelines for Resizing and Zooming Windows explain that the zoom button isn’t actually supposed to expand the window to fill the screen, but toggle the window between a standard application-defined size and a user-defined size. Regardless of what the human interface guidelines say, though, I still don’t like what it does, and I’m apparently not the only person. There are hundreds of pages asking how to fix it.

In case you’re wondering, the bookmarklet is:

javascript:self.moveTo(0,0);self.resizeTo(screen.availWidth,screen.availHeight);

I simply put the bookmarklet first in my bookmark bar, which lets me maximize Safari by hitting ⌘1. I didn’t write this little snippet of Javascript, but I can’t remember where I found it and there are versions of it all over the Web.

So, back to the real story. Upon further investigation, I determined that the bookmarklet only works when the Safari window contains a single tab. If the window has more than one tab, the bookmarklet doesn’t work. I wasn’t the only one who noticed this bug, as I found the post Safari 4 breaks bookmarklets? on This Is the Green Room.

I figured the problem would be fixed when the final version of Safari 4 was released, but when that happened this past week, the problem remained. Googling a bit, I found WebKit bug 24218, which notes that the Javascript window.moveTo and resizeTo methods do not work in Safari windows with more than one tab.

Since the bug was marked unconfirmed and didn’t have any activity on it for months, I decided to download the WebKit source and try to track down the cause of the problem. I checked out the WebKit code and built it according to the instructions. After digging a bit and setting some breakpoints, I figured out that the following method in WebCore/page/DOMWindow.cpp implements window.moveTo in the Javascript engine:

void DOMWindow::moveTo(float x, float y) const
{
if (!m_frame)
return;

Page* page = m_frame->page();
if (!page)
return;

if (m_frame != page->mainFrame())
return;

FloatRect fr = page->chrome()->windowRect();
FloatRect sr = screenAvailableRect(page->mainFrame()->view());
fr.setLocation(sr.location());
FloatRect update = fr;
update.move(x, y);
// Security check (the spec talks about UniversalBrowserWrite to disable this check…)
adjustWindowRect(sr, fr, update);
page->chrome()->setWindowRect(fr);
}

This method basically does some adjustments with the screen and window rectangles, and then finally invokes Chrome::setWindowRect to perform the actual move of the window. Stepping through the method when clicking the bookmarklet with one and two tabs showed that all the rectangle calculations were resulting in the same values regardless of the number of tabs. The invocation of setWindowRect simply wasn’t moving the window when there was more than one tab.

After a bit of indirection through some other classes, Chrome::setWindowRect calls WebChromeClient::setWindowRect in mac/WebCoreSupport/WebChromeClient.mm:

void WebChromeClient::setWindowRect(const FloatRect& rect)
{
NSRect windowRect = toDeviceSpace(rect, [m_webView window]);
[[m_webView _UIDelegateForwarder] webView:m_webView setFrame:windowRect];
}

This method basically translates the rectangle coordinates between the scale used by the window and the WebView itself, and then sends setFrame:windowRect: to WebUIDelegate. This delegate is the bridge between WebKit’s Javascript engine and the actual Cocoa browser window (I think), and WebKit is passing it the correct coordinates to move the window. WebUIDelegate, though, simply doesn’t move the window as it should when there is more than one tab, which leads me to believe the bug is in Safari itself, not WebKit.

I updated the WebKit bug and filed a new bug for Safari on Radar. I thought for a moment that this behavior might actually be intentional, to prevent pages from resizing the browser window via Javascript when there are other open tabs that would be affected. But since the bookmarklet works in Safari 3, I tend to doubt this new behavior is by design as it would break a lot of existing Javascript. Scripts embedded in an actual page are similarly unable to move or resize the window when there’s more than one tab open, so the problem doesn’t have anything to do with the Javascript being in a bookmarklet.

Trackback Comment

A good example of why this might be intentional is something like Adobe’s download manager. Safari has a weird way of handling whether a link is opened in a new window or a new tab. In the case of adobe’s downloader, it gets opened in a new tab, and then tries to resize down to a smaller download window size - which would kill the rest of your tabs.

Whatever the issue is, every other browser on the planet seems to work fine - so Safari needs to figure it out.

Maciej Stachowiak

Sunday, June 14, 2009
3:25 am

Doesn’t sound intentional to me. What’s the Radar bug number?

I wonder if this is related to a problem I’ve had with the official Delicious bookmarklet ever since I started using Safari 4. In Safari 3, the bookmarklet caused a new, small window to pop up with the Delicious.com tagging stuff inside. But now in Safari 4, the same bookmarklet creates a new tab, instead, and doesn’t resize anything. (At least in this instance the fact that it doesn’t resize is a good thing.)

I should note that this behavior with the Delicious bookmarklet is the same regardless of having multiple or just one tab open.

@Maciej: The Radar bug number is 6970303.

@joem: The problem only occurs when there is more than one tab in the window, so I think whatever’s going on with the Delicious bookmarklet is something different. What’s the Javascript in it, out of curiosity?

Not sure if this’ll come through your comment system, but here goes. This is the Delicious bookmarklet:

javascript:(function(){f=’http://delicious.com/save?url=’+encodeURIComponent(window.location.href)+’&title=’+encodeURIComponent(document.title)+’&v=5&’;a=function(){if(!window.open(f+’noui=1&jump=doclose’,'deliciousuiv5′,’location=yes,links=no,scrollbars=no,toolbar=no,width=550,height=550′))location.href=f+’jump=yes’};if(/Firefox/.test(navigator.userAgent)){setTimeout(a,0)}else{a()}})()

I guess the window.open bit should be making a new window instead of a new tab, but it isn’t.

Yeah. Sounds like it.

Seen any progress on your bug report? I also filed one with Apple (#6970616) but haven’t seen a peep on it.

This is so annoying, we need a fix. :sad2:

@samiam: Yeah, they marked mine as a duplicate of #6537031, which they said they are aware of and fixing. We’re at Safari 4.0.2, though, and it’s still not currently fixed.

Still broken in 4.0.3.

This is crazy… still broken in latest Safari… what?!!

If you’re running your own WebKit build, you can override Safari’s behavior. Modify WebChromeClient::setWindowRect() in WebChromeClient.mm:

void WebChromeClient::setWindowRect(const FloatRect& rect)
{
NSRect windowRect = toDeviceSpace(rect, [m_webView window]);
[[m_webView _UIDelegateForwarder] webView:m_webView setFrame:windowRect];

// Safari ignores window.resize/move if multiple tabs are open.
// We can force the resize with this snippet from WebDefaultUIDelegate.m.
[[m_webView window] setFrame:windowRect display:YES];
}

I’ve barely tested it, but it seems to work well.

And… Safari 4.0.5—still broken.

Thursday, March 18, 2010
06:21pm