A Weblog by Ben Goodger

September 27, 2008

I am in Denmark this week.

September 16, 2008

In Chromium, we use custom frame windows on Windows XP, and on Windows Vista without DWM compositing enabled. We do something slightly different when DWM compositing is turned on, which I'll cover in a separate post. Right now, I'm going to focus on how we built and are building the custom window frame for Windows XP. There are two components to this work, rendering the contents of the window and making Windows happy.

Rendering

To render the UI, including the browser window chrome, we use a lightweight UI framework that we call ChromeViews. ChromeViews is a set of C++ classes that provides a layout, rendering and event processing system similar to NSView, and the rendering classes used by popular layout engines like WebKit and Gecko. At the core is the concept of a View, which simply represents a rectangle. This rectangle can paint itself, lay out child views, and receive various kinds of events based on user input. By subclassing this View object we are able to develop the various bits of UI that Chrome uses. Each window is composed of a hierarchy of these View objects. The bridge between the native OS window and the View hierarchy is an object called a ViewContainer, which hosts the RootView that owns the hierarchy. The ViewContainer is an abstract interface that is typically implemented by HWNDViewContainer, which wraps a windows HWND. The window procedure for this HWND receives various messages, translates some into ChromeViews events and fires them into the hierarchy, paints the Views into a bitmap device context using a graphics API called Skia, and does various other things to make Windows happy.

Let's take a look at the browser window, since it's an interesting and complex example of a custom frame window. At the top level there's a View called the RootView that contains the rest of the browser UI. Nested in layers beneath this are other Views like the TabStrip, the ToolbarView, Buttons, and so on and so forth.

But rendering the contents of the window is only part of the story. Since we wanted a custom look for our UI, we wanted to draw our own title bars and sizing borders too. In fact, for our browser window, we wanted to do something even stranger - we wanted to have the tabs living up in the title bar of the window - where they'd form the distinctive "skyline" of the browser. On Windows, the window title bar (called the "caption"), the sizing borders and other window controls are referred to as the "non-client area". The stuff inside them is called the "client area". We were throwing a spanner into works by having a non-rectangular client area that intrudes into the non-client area.

The first step was to keep things simple. We don't treat the non-client area as a separate set of stuff to paint. Rather, the non-client area is just another View in the same hierarchy that hosts the contents of the window. This had some implications though on how we implemented a ViewContainer that could host this view hierarchy to match the look we wanted.

Keeping Windows Happy

In the first implementation, which is what was shipped with the Google Chrome beta by the way, we create a Windows HWND with very basic window styles - because the ChromeViews hierarchy contained within is providing all the non-client area, we wanted to suppress things like WS_CAPTION etc. To make the portions of the window title bar area not occupied by tabs act like a title bar though, messages like WM_NCHITTEST were handled to determine if the mouse was in fact in a blank area, and if so HTCAPTION was returned. On the trunk now we are now somewhat more sophisticated about this - we establish hit-test masks for the tabs that closely follow their shape such that clicking in the blue area where two tabs meet is treated like a click to the title bar!

Because we weren't a frame window, some things like window resizing had to be implemented by hand. This had some undesirable side-effects: when Windows resizes a window it runs a modal loop that synchronously paints the window(s) being revealed... but with our system based entirely on the user dragging the mouse this didn't happen, and so we left artifacts behind as Windows caught up later and repainted the revealed window. We also found we didn't work well with Windows' built in window management features (e.g. window tiling) and various window manager extensions like SplitView didn't work because we either didn't have the appropriate window styles or just did things a little differently. The ViewContainer implementation for the browser window in the Chrome beta is called XPFrame.

This is where a new set of ChromeViews ViewContainers stepped in. Dialog boxes in Chrome on Windows XP are actually hosted in a different kind of window called a CustomFrameWindow. This derives (via a class called Window) from HWNDViewContainer, a general purpose ViewContainer that wraps a HWND. Here's what CustomFrameWindow does differently:

  • Bears the WS_CAPTION, WS_THICKFRAME, and WS_OVERLAPPED window styles, as well as the WS_EX_WINDOWEDGE and WS_EX_APPWINDOW extended styles. By having these styles, CustomFrameWindow gets a lot of the functionality that XPFrame had to do by hand for free (such as specifying the contents and enabled-state of system menu items). This did mean however some gymnastics when painting.
  • Handles WM_NCCALCSIZE, passing the bounds of a "ClientView" as the bounds of the client area. This rectangle is smaller than the window rectangle, just like for a standard top level window. In XPFrame, the client area bounds were equal to the window bounds.
  • Handles WM_NCHITTEST, doing more comprehensive checks to determine if the mouse is over the sizing borders etc. Doing this allows the CustomFrameWindow to be moved and sized by Windows, with all the painting benefits that come with this.
  • Handles WM_NCPAINT, to make sure the client area is painted synchronously with the non-client area, to prevent effects where the window frame appears to "snap in" around its contents.
  • Handles WM_NCLBUTTONDOWN, to make sure clicks to the non-client areas specifying the window controls are translated into events that are sent to the relevant ChromeView.
  • Handles WM_SETCURSOR, to make sure the sizing cursors are set for various hit-test codes.
  • Handles the undocumented WM_NCUAHDRAWCAPTION and WM_NCUAHDRAWFRAME messages to suppress excessive flickering in the title bar area.
  • Update:Handle a growing list of messages, starting with WM_SETTEXT and WM_ENTERMENULOOP, removing the window's WS_VISIBLE style, calling the default window procedure and then adding the WS_VISIBLE style back. Why? Well, sometimes the window gets into a state (not reproducible via a repeatable set of steps, but always eventually) where it begins to paint the standard non-client title bar and controls over the one you rendered. Windows doesn't do this in its default WM_NCPAINT handler, rather it does it directly in its default handlers for these messages. You need to handle these messages and fake invisibility to convince Windows not to do it. If anyone knows other messages that this rule applies to please let me know!

CustomFrameWindow also taps in to a lot of the benefits offered by the Window/WindowDelegate/NonClientView/ClientView framework we use for our dialog boxes. As an example, the shape of our window is actually slightly different from the standard Windows shape. Windows supports rounded corners on its windows with 1-bit transparency by using window regions. This is used on Windows XP Luna to provide rounded corners at the top left and right of each window. We use a custom radius, and the ChromeViews framework allows us to easily specify a custom window shape by creating a Skia path.

Conclusion

In developing frameworks for building UI in Chromium, we've found that trying wherever possible to do what Windows expects means less code, better display integrity and performance, and that the more subtle features some users expect "just work".

A lot of what I've described are things that relate closely to Chromium source code, and design decisions that we've made to meet the objectives we've set for ourselves, and as such they may not be directly applicable to other work. But I do think many of the issues addressed in the implementation of the CustomFrameWindow ViewContainer are potentially useful to others developing custom user interfaces on Windows, especially considering the documentation for some of the things we had to do here is non-existent aside from postings similar to this!

If you've done work like this before, I'd be interested in hearing your thoughts on our approach. Maybe you have some good stories to share!

September 11, 2008

A colleague and I were looking at an interesting bug today. For a little while I've been working on some new frames for the Chromium browser window. These frames are based on the CustomFrameWindow/Window ChromeViews classes that we use to contain much of the UI. Anyway the frames handle various windows non-client messages to implement Chromium's custom looking UI while still using the standard window styles expected of a resizable top level frame window, so that other system level window management functionality works properly (and window management add-ins like SplitView work better too).

One issue preventing the frames from being turned on on the trunk is an annoying flicker in the title bar when switching tabs (or loading a page, or doing just about anything that causes painting to happen near the top of the window). It wasn't happening in an easily reproducible fashion, however it nearly always happened after using the browser for a while. You'd switch tabs and it looked like Windows was trying to paint the standard windows title bar. Then our custom code would kick in and paint our window contents, which would result in an ugly flash.

After trying some elementary logging and poking around in our WM_NCPAINT handling, we looked at the message log sent to each window as the tab was switched, and compared that against the message log sent to a window using the old frames.

The new frames received this string of messages:

S WM_COMMAND wNotifyCode:0400 wID:36648 hwndCtl:073C0244 [wParam:04008F28 lParam:073C0244]
R WM_COMMAND lResult:00000000
S WM_ERASEBKGND hdc:9D0164EB [wParam:9D0164EB lParam:00000000]
R WM_ERASEBKGND fErased:True [lResult:00000001]
S WM_SETTEXT lpsz:0012E8B8 ("Netvibes (200) - Chromium") [wParam:00000000 lParam:0012E8B8]
S message:0x00AE [Unknown] wParam:00000008 lParam:00000000
S WM_GETTEXT cchTextMax:510 lpszText:0012D730 [wParam:000001FE lParam:0012D730]
R WM_GETTEXT cchCopied:25 lpszText:0012D730 ("N") [lResult:00000019]
R message:0x00AE [Unknown] lResult:00000000

The first WM_COMMAND is the Ctrl+PageUp sent to the window when I switched to the previous tab. Then comes the background erase message WM_ERASEBKGND. At this point code in BrowserView2 updates the native window title using WM_SETTEXT. This is done so that the Windows Taskbar title is in sync with the newly selected tab. Then things get interesting.

We receive an "[Unknown]" message 0x00AE, which wraps a WM_GETTEXT message. This message is not sent to the window using the old frames. I theorize that the WM_GETTEXT is being called from some Windows routine that's painting the native titlebar. Since this is all asynchronous that explains the flashing.

A quick Google search reveals that in fact yes, this mystery message 0x00AE is the cause of the problem. Under the hood it maps to WM_NCUAHDRAWCAPTION, which is not #defined in winuser.h. It has a sibling 0x00AF (which apparently maps to WM_NCUAHDRAWFRAME). The forums I found discussing this message all suggest that if you are drawing a custom frame (as we are with Chromium), then you must handle these messages to prevent DefWindowProc from doing it (and trying to paint the caption).

I have applied this change now to HWNDViewContainer and CustomFrameWindow, and things are going smoothly! I'm hoping that the code in ChromeViews will be a good reference for others wanting to build custom window frames on Windows, since the pathway here is far from clear. I may do another post later on describing all the things that need to be done, but in the mean time you can just look at the code in custom_frame_window.cc/h and go from there. At the very least, we are another step closer to turning on --magic_browzR by default!

September 02, 2008

Google Chrome has launched, download it now. The source is available under a permissive BSD style license. Find out more at the Chromium website and blog.

I love browsers, and so I'm really excited that this project and the amazing team that I've been lucky enough to be a part of has finally shipped.

July 10, 2008

Fake Steve may be closing shop, but at least we now have Linux Hater...

March 21, 2008

Back when we were building the Application Update Service for Firefox 1.5, I decided it would be a good idea to show users an informational dialog box letting them know that an update was available, and that they would be updated the next time they restarted. This turns out to have been one of those miserable user interface decisions that as a developer you really wish you hadn't made.

A familiar sight for Firefox users

This dialog box is so annoying, and it's compounded by the fact that when the user clicks "Later" - basically "Sod off you stupid dialog box" the situation is made worse by another dialog box telling the user that they will be updated anyway the next time they restart. At this point the natural reaction is "I DON'T CARE GO AWAY".

Software Update is something that users expect to be pretty transparent, and the fact that it isn't in a lot of software is a cause of annoyance (and occasional hilarity).

One of the worst offenders I think is Adobe. Today I'm going to bitch about their updater. It pops up every time Crap-o-bat is started, offering some cryptically worded and I'm convinced irrelevant update to one of the myriad pieces of shared code junk Adobe software installs onto my computer. In general, the Crap-o-bat user experience is among the worst of desktop software, but I'll stay focused on the updater. I swat this annoying dialog box away over and over, until eventually other circumstances cause me to have to restart the computer.

At startup, Windows does its usual charming thing of showing the desktop approximately 3-4 minutes before anything's actually interactive, and so I sit here dumbly waiting for all the startup items and tray icons to load. After a short while, the Adobe updater appears. I figure what the heck, I've rebooted, let's let it do its thing.

The Adobe Updater

One of the first rules of progress meters is to make sure that progress is shown immediately and that it's constantly updated, even if the steps are very small. This provides the user with a sense of satisfaction that something is happening and that they are slowly getting towards their goal state. The Adobe Updater appears and sits for about 2-3 minutes with an empty progress bar. Is it hung? No, the window is still interactive. I wonder if this is not some retarded Windows MSI database checking phase. Eventually the updater begins chugging through each update. The best (and by best I mean worst) part is that Adobe seems to have invented a new kind of progress meter, which shows indeterminate progress within a particular step of a multi-step install progress within a single master progress meter. The updater achieves this by having the progress meter step up, and then once it reaches some arbitrary limit fall back to where it was at the start of the step, and repeats this process over and over until that step is complete. This is very disconcerting to the user since it looks like something is failing causing progress to be reset. Since little in the UI changes, I began to think the updater had jammed and was about to close it until after another 3 minutes or so it continued on to the next step and I figured out what it was doing.

In all, the process took about 20 minutes to complete, which is staggeringly long considering the software being updated. In the process, the updater dropped an unwanted Crap-o-bat icon on my desktop. Why they felt the need to do this I'll never know - especially since Crap-o-bat is one of those pieces of software that actually needs no icons anywhere on your computer, since the way you typically launch it is either through its browser plugin or by double clicking on a PDF file in Explorer. Why their updater felt like their software deserved a spot on my precious desktop is beyond me, except perhaps to think that their marketing department holds users in utter contempt and that simply branding people's computing experiences with unnecessary junk bearing their company name is worthwhile.

March 15, 2008

... and why he's going to be no cake-walk opponent for the Dems this November in his very respectable and reasonable handling of the Baby Jesus: