I am in Denmark this week.
I am in Denmark this week.
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 aViewContainer, which hosts the RootView that owns the hierarchy. The ViewContainer is an abstract interface that is typically implemented byHWNDViewContainer, 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:
CustomFrameWindow also taps in to a lot of the benefits offered by theWindow/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!
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/WindowChromeViews 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_NCPAINThandling, 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:0×00AE [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:0×00AE [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 messageWM_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_GETTEXTmessage. 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 0x00AEis the cause of the problem. Under the hood it maps toWM_NCUAHDRAWCAPTION, which is not #defined in winuser.h. It has a sibling 0x00AF (which apparently maps to WM_NCUAHDRAWFRAME). Theforums 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 andCustomFrameWindow, 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 incustom_frame_window.cc/h and go from there. At the very least, we are another step closer to turning on --magic_browzR by default!
Google Chrome has launched, download it now. The source is available under a permissive BSD style license. Find out more at the Chromium website andblog.
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.