the Chromium logo

The Chromium Projects

UI Mirroring Infrastructure

Objective

One of the goals for the launch of Chromium is to support 40 UI languages; that is, the UI needs to be localized for each language such that any UI text element (menus, titles, labels, etc.) is displayed in the target language. In order to make sure that Chromium can be easily localized to different languages (an effort known as localizability), the Chromium source code is structured such that localizable strings are stored in locale-specific DLLs. This way, the actual UI strings are loaded from the right DLL, depending on the target locale. There is one more localizability aspect which applies only to right-to-left (RTL) languages, such as Hebrew and Arabic. In such languages, translating the strings to the target language is not enough. In order to provide a perfect right-to-left look and feel to the browser, the entire UI needs to be mirrored.

This document starts by providing a brief overview of the UI mirroring technology in general and in particular the Windows support for UI mirroring. Then, the newly added ChromeViews mirroring infrastructure is described. Using this new infrastructure, the Chromium UI elements can be easily mirrored for RTL locales. Finally, and most importantly, the document provides a set of guidelines for writing Chromium UI elements that don't break when mirrored in RTL UIs.

Windows UI mirroring architecture

An example

The Windows operating system itself has been localized for a large set of languages, including right-to-left languages like Hebrew. Thus, users of the Hebrew version of Windows are presented with a mirrored version of the English OS UI. Before we examine the mirroring Windows API available to application developers, it will be helpful to compare the mirrored and the non-mirrored versions of a fundamental Windows UI component - Windows Explorer. Looking at the manner in which the Windows OS mirrors its core UI elements will shed some light on the ChromeViews Mirroring Infrastructure design. The following diagram shows the English version of Windows Explorer we are all familiar with:

image

The first thing to note about the English version is that left alignment is used for all the text elements. Also, there are two groups of highlighted UI elements (the red group and the black group) that behave differently when displayed in a mirrored UI. The elements in both groups contain an image (a bitmap or an icon). All the elements in the red group (elements 1, 2 and 3) contain direction sensitive images; that is, the images in these elements must be flipped horizontally when displayed in mirrored UIs. Not flipping direction sensitive graphics can cause various UI problems. For example, not flipping the resize bitmap (item 3) will cause the UI to appear incorrectly - a Southeast-oriented resize bar will appear in the Southwest corner of the window. Not flipping the back button (item 1) will result in a confusing user experience - the button will say 'Back' but the arrow will point to a direction indicating going 'Forward'. All the elements in the black group (elements A, B and C) do not contain direction sensitive images. Thus, the images in these elements must look the same in mirrored and non-mirrored UIs. Generally, legal trademarks, logos, favicons, etc. are not direction sensitive and flipping them horizontally in mirrored UIs does not make sense. Following is the Hebrew (and therefore mirrored) version of Windows Explorer:

image

The first thing to note about the Hebrew version is that right alignment is used for all the text elements (English and Hebrew). Also, all the UI text (menus, buttons, status bar info, etc.) is translated to Hebrew. All the UI elements and sub-elements are mirrored recursively. For example, the position of the folder tree view is mirrored (it is on the right-hand side of the window and not the left-hand side). The elements in the tree view (the plus signs, the directory icons, etc.) also appear in their mirrored position within the tree view. Note how even though the position of all the highlighted elements is mirrored, the elements in the black group look exactly the same as they did in the English version while the elements in the red group are flipped horizontally.

How does Windows do it?

The Windows API provides a set of useful techniques for dealing with RTL UI localization. The goal of the Windows UI mirroring API is to allow GUI developers to write UI code without having to worry about mirroring the UI for right-to-left locales. The UI is created using well known Windows GUI constructs (HWNDs, Device Contexts, Dialog Boxes, etc.) and then an RTL layout property can be applied to individual elements in order to mirror their UI. For example, the property WS_EX_LAYOUTRTL can be applied to an HWND in order to mirror the UI of the underlying window. When an RTL layout is applied to a GUI element, the element's coordinate system is transformed in the following manner:

image

When using an RTL layout, the right-most horizontal coordinate value is 0 (instead of the leftmost) and the coordinate values increase (instead of decrease) when moving from right to left on the X axis. Using this transformation, a UI element the application positions in the top left corner will be positioned on the top right corner when the UI is mirrored. The concept of coordinate transformation is extremely powerful since it doesn't require the GUI application's logic to change in order to support mirroring. In reality, however, more often than not mirroring a GUI application involves much more than just setting a few flags. There are a few bad programming practices that cause the GUI application to appear incorrectly when mirrored. For the most part these have to do with incorrect conversions between screen and window coordinates. To learn more about the Windows mirroring API and about how to avoid programming techniques that don't work well in mirrored UIs, see the following MSDN article.

ChromeViews mirroring infrastructure

Chromium has a portable, object oriented UI framework (aka ChromeViews) that allows developers to write Chromium UI elements. In order to allow the Chromium UI to be mirrored easily, a mirroring infrastructure needed to be added to the ChromeViews framework. The inherent Windows support could not be used by itself to mirror the Chromium UI for a few different reasons:

ChromeViews::View mirroring support

The Chromium mirroring infrastructure and API resides mostly in the ChromeViews::View class since it is the base class for the majority of the Chromium browser UI elements. Before describing the interface, it is important to note that whenever we refer to the concept of 'locale' or 'current locale' we refer to the Chromium UI language. The locale concept entails more than just the UI language, but for the purpose of explaining the mirroring infrastructure, it is OK to ignore the other locale aspects or the logic Chromium uses in order to set the default locale.

Just like the Windows mirroring API, the corresponding Chromium API is designed in a way that frees individual GUI developers from the burden of programmatically mirroring UI elements and sub elements. However, the API has certain limitations and in some cases more mirroring-related code needs to be written for specific Chromium UI elements. Chromium View subclasses that follow a certain set of conventions (described in How to write RTL-ready Chromium UI, below), are going to work well in mirrored UIs and won't need to have additional code for handling right-to-left layout issues. For a complete documentation of the mirroring API, refer to the comments in view.h. Here is a brief description of the new mirroring functions:

NOTE: unlike Windows, the mirroring infrastructure in Chromium does not rely on coordinate transformations. Regardless of the locale, mouse events are always relative to the top left corner of the View.

The best way to demonstrate how the API works is to look at a hypothetical View subclass named Foo that contains three child Views. Here is the code snippet from the header file:

class BitmapView : public ChromeViews::View { ... public: BitmapView() { // By default, we want to flip the canvas so that the bitmap is flipped // when the UI layout is right-to-left. EnableCanvasFlippingForRTLUI(true); } virtual void Paint(ChromeCanvas* canvas) { canvas->DrawBitmapInt(bitmap_, 0, 0); } private: // The bitmap we draw on the canvas. SkBitmap bitmap_; ... } class Foo : public ChromeViews::View { public: // Layout the three child bitmap Views diagonally from top left to bottom // right. virtual void Layout() { ... } private: BitmapView* bitmap_one_; BitmapView* bitmap_two_; BitmapView* bitmap_three_; ... }

The Foo class contains three child Views that are laid out diagonally from top left to bottom right. Each child is an instance of the BitmapView subclass which represents a single bitmap. The following diagram visualizes the impact of different API calls on the manner in which Foo is rendered. Note that in the example, children one, two, and three contain a bitmap with the corresponding number.

image

It is important to emphasize that even though the mirroring API contains quite a few elements, View subclasses don't need to explicitly use the API in the majority of the cases. As an example, consider BrowserToolbarView, which is a View subclass with several child Views. Here is a snippet from toolbar_view.h with the definition of the different Toolbar controls:

class BrowserToolbarView : public ChromeViews::View, public Menu::BaseControllerDelegate, public ChromeViews::ViewMenuDelegate, public ChromeViews::DragController, public LocationBarView::Delegate { ... // Controls ChromeViews::Button* back_; ChromeViews::Button* forward_; ChromeViews::Button* reload_; ChromeViews::ToggleButton* star_; LocationBarView* location_bar_; GoButton* go_; ChromeViews::MenuButton* page_menu_; ChromeViews::MenuButton* app_menu_; ... }

All the toolbar's UI sub elements are child Views and the toolbar class does not render any UI elements itself. Instead, it delegates painting to its sub elements (we'll explain later why this is the preferred way to structure a View subclass). Here is a snippet from the code in BrowserToolbarView::Layout():

void BrowserToolbarView::Layout() { ... back_->GetPreferredSize(&sz); back_->SetBounds(kControlIndent, kControlVertOffset, sz.cx, sz.cy); forward_->GetPreferredSize(&sz); forward_->SetBounds(back_->GetX() + back_->GetWidth(), kControlVertOffset, sz.cx, sz.cy); ... }

The toolbar lays out its child elements using the normal View positioning API (GetX(), GetBounds(), etc.) and does not have to special case the layout for RTL locales. Finally, here is what the toolbar looks like in RTL and LTR locales:

image

The toolbar's code didn't need to change at all in order to appear correctly in an RTL UI. The toolbar is an example of a View subclass for which UI mirroring is taken care off automatically by the new framework. This is unfortunately not the case for all the Chromium UI elements.

Chromium support for native mirroring

Most of the Chromium mirroring infrastructure resides in ChromeViews. However, since Chromium also uses Windows native UI controls, the mirroring infrastructure includes interfaces that allow mirroring these controls easily. Here is a brief description of the interface:

How to write RTL-ready Chromium UI

The mirroring infrastructure described in this document is designed to be transparent to GUI developers. However, this is true only if the UI elements adhere to a certain set of conventions. Not following these rules when writing a UI element generally means that mirroring-specific code will need to be added to the element so that it is displayed correctly in right-to-left locales. This section describes the rules together with examples that demonstrate why not following these rules is problematic. Even though it is discouraged to put mirroring-specific code in a UI element (it is better to just follow the rules), this section shows how to handle mirroring manually in UI components. This is useful for UI component that, for whatever reason, can not be written in a manner that allows the mirroring infrastructure to display the component correctly in RTL locales.

Rule #1: represent UI sub elements as child Views

The Chromium toolbar example presented earlier is an example of a Chromium View subclass that contains several child Views as its sub elements. The toolbar lays out the sub elements, but it does not do any rendering. Instead, rendering is done by the individual sub elements. In other words, for the toolbar, the 'leaf' elements in the UI tree are the only elements that draw graphics on the canvas during View::Paint(). This method plays well with the mirroring infrastructure since the coordinate transformation can be easily applied to the bounds of the child elements. One of the Chromium UI components that does not adhere to this rule is the Tab (see chrome/browser/tabs/tab.h). The Tab contains three UI sub elements. A 'close' button, a title and a favicon. The button is represented as a ChromeView::Button child View. The title and the favicon are both drawn directly on the canvas by the Tab class during View::Paint(). While the tabs look correctly on left-to-right locales, they don't on RTL locales:

image

As the image shows, the button is positioned correctly on the left-hand side of the tab because during Tab::Layout(), it is positioned on the right-hand side (which becomes left when applying the coordinate transformation). The title and the favicon, on the other hand, are not positioned in the same manner since they are not child views. During Tab::Paint(), they are still drawn on the left-hand side, instead of the right-hand side. Readers of this (already long and boring) document are probably thinking the following right now: "This can probably be solved if View::EnableCanvasFlippingForRTLUI(true); is invoked in Tab's constructor so that the canvas is transformed.". Well, think again:

image

Flipping the canvas makes sense only for 'leaf' Views that draw graphics on their entire area. When drawing different elements on the canvas, it is not possible to use the mirroring infrastructure in order to control the appearance of each element. The best way to fix the problem is to represent the title and the favicon as child views. If that's not possible, the position of these elements needs to be mirrored manually:

favicon_bounds_.set_x(MirroredLeftPointForRect(favicon_bounds_)); title_bounds_.set_x(MirroredLeftPointForRect(title_bounds_));

With this change, the tabs are displayed properly:

image

Rule #2: don't make assumptions such as top-left is always northwest.

When your View subclass is being mirrored, its coordinate system is transformed and therefore if you are processing a mouse event for the point (0,0), the actual mouse position is the Northeast corner of your control. Thus, you should not write your GUI code assuming a left-to-right coordinate system. If the logic of your code depends on the actual position of the mouse event, then you should use the mirroring API in order to find out whether or not the coordinate system is transformed. See chrome\views\custom_frame_window.cc for an example.

More rules will be added later on as I continue analyzing the cause of the most common Chromium mirroring bugs. For now, try to adhere to the initial rules..

How to test a mirrored Chromium UI element

If you write a new Chromium UI element or modify an existing element, it is highly important that you make sure the element is mirrored correctly in right-to-left locales. In order to do that, you can either change the UI language of Chromium via the Options dialog, or you can run Chromium from the command line such that the Hebrew UI is used:

Windows: chrome.exe --lang=he

Linux: Confirm the he_IL locale is installed on the system by running `locale -a` to list. Then: LANG=he_IL.UTF-8 LANGUAGE=he_IL chrome

Mac: Use the Language and Region settings to change OS X to Hebrew. Then restart Chrome.

Once you do that, you can look at your control and make sure it doesn't have layout bugs. Note that your machine probably doesn't include the files required for displaying right-to-left languages and therefore all the Hebrew strings will be rendered from left to right which will make them unreadable. It will also make certain Unicode control characters to appear as little squares surrounding the text. This does not impact the layout of the Chromium UI elements so it shouldn't prevent you from making sure your UI element is properly laid out. If you want the Hebrew strings to appear correctly and you want the annoying squares to go away, you can easily install the required Windows right-to-left language files by going to Control Panel > Regional and Language Options, selecting the Languages tab, and selecting the option to Install files for complex script and right-to-left languages (including Thai). Note that installing the right-to-left files requires a restart.

I am still trying to figure out if there is a way to write a UI test that makes sure UI mirroring is not broken so that people can make sure they don't break anything before a check-in. Suggestions are welcome!