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.
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
orui::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
orui::Window
). -
The
ui::mojom::WindowTreeClient::OnEmbed
function is invoked whenEmbed
is called on the connectedui::mojom::WindowTree
. The window parameter will be set as the window root (aura:WindowMus
orui: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 ofOnEmbed
being called on the window tree client. -
The
ui::mojom::WindowTree::NewWindow
function can be used to create a new window. It will callOnChangeCompleted
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 callOnTopLevelCreated
(orOnChangeCompleted
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.
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!