Frédéric Wang Yet another non-exponentially growing weblog

About Me  Blog Archive

Mus Window System

Update 2017/02/01: The day I published this blog post, the old mus client library was removed. Some stack traces were taken during the analysis Antonio & I made in fall 2016, but they are now obsolete. I’m updating the blog in order to try and clarify things and fix possible inaccuracies.

TL; DR

Igalia has recently been working on making Chromium Desktop run natively on Ozone/Wayland thanks to support from Renesas. This is however still experimental and there is a lot of UI work to do. In order to facilitate discussion at BlinkOn7 and more specifically how browser windows are managed, we started an analysis of the Chromium Window system. An overview is provided in this blog post.

Introduction

Antonio described how we were able to get upstream Chromium running on Linux/Ozone/Wayland last year. However for that purpose the browser windows were really embedded in the “ash” environment (with additional widgets) which was itself drawn in an Ozone/Wayland window. This configuration is obviously not ideal but it at least allowed us to do some initial experiments and to present demos of Chromium running on R-Car M3 at CES 2017. If you are interested you can check our ces-demos-2017 and meta-browser repositories on GitHub. Our next goal is to have all the browser windows handled as native Ozone/Wayland windows.

In a previous blog post, I gave an overview of the architecture of Ozone. In particular, I described classes used to handle Ozone windows for the X11 or Wayland platforms. However, this was only a small part of the picture and to understand things globally one really has to take into account the classes for the Aura window system and Mus window client & server.

Window-related Classes

Class Hierarchy

A large number of C++ classes and types are involved in the chromium window system. The following diagram provides an overview of the class hierarchy. It can merely be divided into four parts:

  • In blue, the native Ozone Windows and their host class in Aura.
  • In red and orange, the Aura Window Tree.
  • In purple, the Mus Window Tree (client-side). Update 2017/02/01: These classes have been removed.
  • In green and brown, the Mus Window Tree (server-side) and its associated factories.

Windowing System

I used the following convention which is more or less based on UML:

  • Rectangles represent classes/interfaces while ellipses represent enums, types, defines etc. You can click them to access the corresponding C++ source file.
  • Inheritance is indicated by a white arrow to the base class from the derived class. For implementation of Mojo interfaces, dashed white arrows are used.
  • Other associations are represented by an arrow with open or diamond head. They mean that the origin of the arrow has a member involving one or more instances of the pointed class, you can hover over the arrow head to see the name of that class member.

Native Ozone Windows (blue)

ui::PlatformWindow is an abstract class representing a single window in the underlying platform windowing system. Examples of implementations include ui::X11WindowOzone or ui::WaylandWindow.

ui::PlatformWindow can be stored in a ui::ws::PlatformDisplayDefault which implements the ui::PlatformWindowDelegate interface too. This in turn allows to use platform windows as ui::ws::Display. ui::ws::Display also has an associated delegate class.

aura::WindowTreeHost is a generic class that hosts an embedded aura:Window root and bridges between the native window and that embedded root window. One can register instance of aura::WindowTreeHostObserver as observers. There are various implementations of aura::WindowTreeHost but we only consider aura::WindowTreeHostPlatform here.

Native ui::PlatformWindow windows are stored in an instance of aura::WindowTreeHostPlatform which also holds the gfx::AcceleratedWidget to paint on. aura::WindowTreeHostPlatform implements the ui::PlatformWindowDelegate so that it can listen events from the underlying ui::PlatformWindow.

aura::WindowTreeHostMus is a derived class of aura::WindowTreeHostPlatform that has additional logic make it usable in mus. One can listen changes from aura::WindowTreeHostMus by implementing the aura::WindowTreeHostMusDelegate interface.

Aura Windows (orange)

aura::Window represents windows in Aura. One can communicate with instances of that class using a aura::WindowDelegate. It is also possible to listen for events by registering a list of aura::WindowObserver.

aura::WindowPort defines an interface to enable aura::Window to be used either with mus via aura::WindowMus or with the classical environment via aura::WindowPortLocal. Here, we only consider the former.

WindowPortMus holds a reference to a aura::WindowTreeClient. All the changes to the former are forwarded to the latter so that we are sure they are propagated to the server. Conversely, changes received by aura::WindowTreeClient are sent back to WindowPortMus. However, aura::WindowTreeClient uses an intermediary aura::WindowMus class for that purpose to avoid that the changes are submitted again to the server.

Aura Window Tree Client (red)

aura::WindowTreeClient is an implementation of the ui::mojom::WindowManager and ui::mojom::WindowTreeClient Mojo interfaces for Aura. As in previous classes, we can associate to it a aura::WindowTreeClientDelegate or register a list of aura::WindowTreeClientObserver.

aura::WindowTreeClient also implements the aura::WindowManagerClient interface and has as an associated aura::WindowManagerDelegate. It has a set of roots aura::WindowPortMus which serve as an intermediary to the actual aura:Window instances.

In order to use an Aura Window Tree Client, you first create it using a Mojo connector, aura::WindowTreeClientDelegate as well as optional aura::WindowManagerDelegate and Mojo interface request for ui::mojom::WindowTreeClient. After that you need to call ConnectViaWindowTreeFactory or ConnectAsWindowManager to connect to the Window server and create the corresponding ui::mojom::WindowTree connection. Obviously, AddObserver can be used to add observers.

Finally, the CreateWindowPortForTopLevel function can be used to request the server to create a new top level window and associate a WindowPortMus to it.

Mus Window Tree Client (purple)

Update 2017/02/01: These classes have been removed.

ui::WindowTreeClient is the equivalent of aura:WindowTreeClient in mus. It implements the ui::mojom::WindowManager and ui::mojom::WindowTreeClient Mojo interfaces. It derives from ui::WindowManagerClient, has a list of ui::WindowTreeClientObserver and is associated to ui::WindowManagerDelegate and ui::WindowTreeClientDelegate.

Regarding windows, they are simpler than in Aura since we only have one class ui::Window class. In a similar way as Aura, we can register ui::WindowObserver. We can also add ui::Window as root windows or embedded windows of ui::WindowTreeClient.

In order to use a Mus Window Tree Client, you first create it using a ui::WindowTreeClientDelegate as well as optional ui::WindowManagerDelegate and Mojo interface request for ui::mojom::WindowTreeClient. After that you need to call ConnectViaWindowTreeFactory or ConnectAsWindowManager to connect to the Window server and create the corresponding ui::mojom::WindowTree connection. Obviously, AddObserver can be used to add observers.

Finally, the NewTopLevelWindow and NewWindow functions can be used to create new windows. This will cause the corresponding functions to be called on the connected ui::mojom::WindowTree.

Mus Window Tree Server (green and brown)

We just saw the Aura and Mus window tree clients. The ui::ws::WindowTree Mojo interface is used to for the window tree server and is implemented in ui::ws::WindowTree. In order to create window trees, we can use either the ui::ws::WindowManagerWindowTreeFactory or the ui::ws::WindowTreeFactory which are available through the corresponding ui::mojom::WindowManagerWindowTreeFactory and ui::mojom::WindowTreeFactory Mojo interfaces.

ui::ws::WindowTreeHostFactory implements the ui::ws::WindowTreeHostFactory Mojo interface. It allows to create ui::mojom::WindowTreeHost, more precisely ui::ws::Display implementing that interface. Under the hood, ui::ws::Display created that way actually have an associated ws::DisplayBinding creating a ui::ws::WindowTree. Of course, the display also has a native window associated to it.

Mojo Window Tree APIs (pink)

Update 2017/02/01: The Mus client has been removed. This section only applies to the Aura client.

As we have just seen in previous sections the Aura and Mus window systems are very similar and in particular implement the same Mojo ui::mojom::WindowManager and ui::mojom::WindowTreeClient interfaces. In particular:

  • The ui::mojom::WindowManager::WmCreateTopLevelWindow function can be used to create a new top level window. This results in a new windows (aura::Window or ui::Window) being added to the set of embedded windows.

  • The ui::mojom::WindowManager::WmNewDisplayAdded function is invoked when a new display is added. This results in a new window being added to the set of window roots (aura::WindowMus or ui::Window).

  • The ui::mojom::WindowTreeClient::OnEmbed function is invoked when Embed is called on the connected ui::mojom::WindowTree. The window parameter will be set as the window root (aura:WindowMus or ui:Window). Note that we assume that the set of roots is empty when such an embedding happens.

Note that for Aura Window Tree Client, the creation of new aura::WindowMus window roots implies the creation of the associated aura::WindowTreeHostMus and aura::WindowPortMus. The creation of the new aura:Window window by aura::WmCreateTopLevelWindow is left to the corresponding implementation in aura::WindowManagerDelegate.

On the server side, the ui::ws::WindowTree implements the ui::mojom::WindowTree mojo interface. In particular:

  • The ui::mojom::WindowTree::Embed function can be used to embed a WindowTreeClient at a given window. This will result of OnEmbed being called on the window tree client.

  • The ui::mojom::WindowTree::NewWindow function can be used to create a new window. It will call OnChangeCompleted on the window tree client.

  • The ui::mojom::WindowTree::NewTopLevelWindow function can be used to request the window manager to create a new top level window. It will call OnTopLevelCreated (or OnChangeCompleted in case of failure) on the window tree client.

Analysis of Window Creation

Chrome/Mash

In our project, we are interested in how Chrome/Mash windows are created. When the chrome browser process is launched, it takes the regular content/browser startup path. That means in chrome/app/chrome_main.cc it does not take the ::MainMash path, but instead content::ContentMain. This is the call stack of Chrome window creation within the mash shell:

#1 0x7f7104e9fd02 ui::WindowTreeClient::NewTopLevelWindow()
#2 0x5647d0ed9067 BrowserFrameMus::BrowserFrameMus()
#3 0x5647d09c4a47 NativeBrowserFrameFactory::Create()
#4 0x5647d096bbc0 BrowserFrame::InitBrowserFrame()
#5 0x5647d0877b8a BrowserWindow::CreateBrowserWindow()
#6 0x5647d07d5a48 Browser::Browser()
...

Update 2017/02/01: This has changed since the time when the stack trace was taken. The BrowserFrameMus constructor now creates a views:DesktopNativeWidgetAura and thus an aura::Window.

The outtermost window created when one executes chrome --mash happens as follows. When the UI service starts (see Service::OnStart in services/ui/service.cc), an instance of WindowServer is created. This creations triggers the creation of a GpuServiceProxy instance. See related snippets below:

void Service::OnStart() {
  (...)
  // Gpu must be running before the PlatformScreen can be initialized.
  window_server_.reset(new ws::WindowServer(this));
  (..)
}

WindowServer::WindowServer(WindowServerDelegate* delegate)
: delegate_(delegate),
  (..)
  gpu_proxy_(new GpuServiceProxy(this)),
  (..)
{ }

GpuServiceProxy, then, starts off the GPU service initialization. By the time the GPU connection is established, the following callback is called: GpuServiceProxy::OnInternalGpuChannelEstablished. This calls back to WindowServer::OnGpuChannelEstablished, which calls Service::StartDisplayInit. The later schedules a call to PlatformScreenStub::FixedSizeScreenConfiguration. This finally triggers the creation of an Ozone top level window via an ui::ws::Display:

#0 base::debug::StackTrace::StackTrace()
#1 ui::(anonymous namespace)::OzonePlatformX11::CreatePlatformWindow()
#2 ui::ws::PlatformDisplayDefault::Init()
#3 ui::ws::Display::Init()
#4 ui::ws::DisplayManager::OnDisplayAdded()
#5 ui::ws::PlatformScreenStub::FixedSizeScreenConfiguration()
(..)

On the client-side, the addition of a new display will cause aura::WindowTreeClient::WmNewDisplayAdded to be called and will eventually lead to the creation of an aura::WindowTreeHostMus with the corresponding display id. As shown on the graph, this class derives from aura::WindowTreeHost and hence instantiates an aura::Window.

Mus demo

The Chromium source code contains a small mus demo that is also useful do experiments. The demo is implemented via a class with the following inheritance:

class MusDemo : public service_manager::Service,
                public aura::WindowTreeClientDelegate,
                public aura::WindowManagerDelegate

i.e. it can be started as Mus service and handles some events from aura:WindowTreeClient and aura::WindowManager.

When the demo service is launched, it creates a new Aura environment and an aura::WindowTreeClient, it does the Mojo request to create an ui::WindowTree and then performs the connection:

#0 aura::WindowTreeClient::ConnectAsWindowManager()
#1 ui::demo::MusDemo::OnStart()
#2 service_manager::ServiceContext::OnStart()

Similarly to Chrome/Mash, a new display and its associated native window is created by the UI:

#0 ui::ws::WindowTree::AddRootForWindowManager()
#1 ui::ws::Display::CreateWindowManagerDisplayRootFromFactory()
#2 ui::ws::Display::InitWindowManagerDisplayRoots()
#3 ui::ws::PlatformDisplayDefault::OnAcceleratedWidgetAvailable()
#4 ui::X11WindowBase::Create()
#5 ui::(anonymous namespace)::OzonePlatformX11::CreatePlatformWindow()
#6 ui::ws::PlatformDisplayDefault::Init()
#7 ui::ws::Display::Init()
#8 ui::ws::DisplayManager::OnDisplayAdded()

The Aura Window Tree Client listens the changes and eventually aura::WindowTreeClient::WmNewDisplayAdded is called. It then informs its MusDemo delegate so that the display is added to the screen display list

#0 DisplayList::AddDisplay
#1 ui::demo::MusDemo::OnWmWillCreateDisplay
#2 aura::WindowTreeClient::WmNewDisplayAddedImpl
#3 aura::WindowTreeClient::WmNewDisplayAdded

…and the animated bitmap window is added to the Window Tree:

#0  ui::demo::MusDemo::OnWmNewDisplay
#1  aura::WindowTreeClient::WmNewDisplayAddedImpl
#2  aura::WindowTreeClient::WmNewDisplayAdded

Update 2017/02/01: Again, aura::WindowTreeClient::WmNewDisplayAdded will lead to the creation of an aura Window.

Conclusion

Currently, both Chrome/Mash or Mus demo create a ui::ws::Display containing a native Ozone window. The Mus demo class uses the Aura WindowTreeClient to track the creation of that display and pass it to the Mus Demo class. The actual chrome browser window is created inside the native Mash window using a Mus WindowTreeClient. Again, this is not what we want since each chrome browser should have its own native window. It will be interesting to first experiment multiple native windows with the Mus demo.

Update 2017/02/01: aura::WindowTreeClient is now always used to create Aura Windows for both external windows (corresponding to a display & a native window) and internal windows. However, we still want all chrome browser to be external windows.

Igalia Logo
Renesas Logo

It seems that the Ozone/Mash work has been mostly done with ChromeOS in mind and will probably need some adjustments to make things work on desktop Linux. We are very willing to discuss this and other steps for Desktop Chrome Wayland/Ozone in details with Google engineers. Antonio will attend BlinkOn7 next week and will be very happy to talk with you so do not hesitate to get in touch!