blob: 30825bb7e5c3235bd56d2534b4cda1d79b3f865b [file] [view]
# Chrome OS D-Bus Usage in Chrome
*** note
**Warning: This document is old & has moved. Please update any links:**<br>
https://chromium.googlesource.com/chromiumos/docs/+/HEAD/dbus_in_chrome.md
***
[D-Bus] is used to perform interprocess communication on Chrome OS. This
document describes how to use D-Bus for communication between Chrome and system
daemons.
> See the [D-Bus Best Practices] document for high-level advice on using D-Bus.
> While that document focuses on how to use D-Bus within system daemons, much of
> it is relevant to Chrome as well.
[TOC]
## Sharing constants
The [system_api] repository contains C++ constants and protocol buffer `.proto`
files that are shared between Chrome and Chrome OS system daemons. This includes
D-Bus service names, paths, and interfaces, signal and method names, and enum
values that are passed as D-Bus arguments.
System daemons essentially always use the latest version of the repository,
while Chrome uses the revision specified in [src/DEPS]. Exercise care when
making use of new constants or removing deprecated constants. To create a change
that updates the version used by Chrome to ToT, run `roll-dep
src/third_party/cros_system_api`.
## Creating Chrome D-Bus services
Receiving method calls or emitting signals requires registering a service.
Chrome registers services using a variety of service names, including
`org.chromium.DisplayService` and `org.chromium.NetworkProxyService`. Services
implement the [chromeos::CrosDBusService::ServiceProviderInterface] interface.
### Code location
The [mus+ash] project is separating Chrome's shell/window-management code (i.e.
`//ash`) from its browser code (i.e. `//chrome`). If a service does not depend
on any code under `//chrome`, its service provider class should live in
[ash/dbus] and be instantiated by [ash_dbus_services.cc].
Services with implementations that depend on `//chrome` should be implemented
within [chrome/browser/chromeos/dbus] and instantiated by
[chrome_browser_main_chromeos.cc].
### Policy files
In order for Chrome to be able to take ownership of a service name and for other
processes to be able to call its methods, each service also needs a `.conf` XML
policy file in [ash/dbus] or [chrome/browser/chromeos/dbus]. Policy files are
loaded by `dbus-daemon` (which implements the system bus) and specify which Unix
users can own service names or call services' methods. Chrome's policy files are
installed to `/opt/google/chrome/dbus` and must each be listed in the
`dbus_service_files` target in [ash/BUILD.gn] or
[chrome/browser/chromeos/BUILD.gn]. (Per the old pattern described above, note
that some policy files instead live in [chromeos/dbus/services].)
See [D-Bus Best Practices] for more information about D-Bus permissions.
## Using system daemons' D-Bus services
To call methods exported by system daemons or observe signals, Chrome uses
`Client` classes located under [chromeos/dbus].
**Chrome's D-Bus-related code is not thread-safe and runs on the UI thread in
the browser process.**
> System daemons must install their own D-Bus policy files granting
> `send_destination` privileges to the `chronos` user in order to receive method
> calls from Chrome. Search for "permissions" in [D-Bus Best Practices] for
> details.
`Client` classes are owned by `chromeos::DBusClientCommon` (instantiated in all
processes) and `chromeos::DBusClientsBrowser` (instantiated only in the browser
process), which are both owned by `chromeos::DBusThreadManager`. `Client`
classes are currently accessed via the `DBusThreadManager` singleton.
When adding a new `Client` class, please follow the patterns used by the
existing code:
### Define real and fake implementations
For a service named "foo", define a `FooClient` interface in `foo_client.h`. In
`foo_client.cc`, declare and define a `FooClientImpl` class that implements the
interface. Add `fake_foo_client.h` and `fake_foo_client.cc` files defining a
`FakeFooClient` implementation that can be used in unit tests and when running a
Chrome OS build of Chrome on a workstation where the system daemons aren't
present.
### Keep your `ClientImpl` class minimal
Ideally, client classes should only connect to signals, call methods, and
serialize and deserialize D-Bus message arguments. The real implementations of
client interfaces aren't exercised by unit tests, so keep your actual logic in
the class that uses the client interface, where it can be tested using the fake
client implementation.
More concretely, a `Client` interface should expose public methods with the same
names as the corresponding D-Bus methods and optionally define a nested
`Observer` interface that can be used to receive notifications about signals.
`FakeClientImpl` can additionally expose setters that specify canned values to
be returned by methods and `NotifyObserversAboutSomeSignal` methods that call a
method on all observers to simulate the receipt of a signal.
If a D-Bus method takes a serialized protocol buffer as an argument, the client
class's corresponding method should take a const reference to that protobuf as
its argument (rather than individual args corresponding to the protobuf's
fields). Observer interfaces and method callbacks should also take protobuf args
when possible. Search for "complex messages" in [D-Bus Best Practices] for
additional information.
### Method calls must be asynchronous
Since a `Client` class's code runs on Chrome's UI thread, D-Bus method calls to
system daemons (which may be otherwise occupied or even hanging) must be
asynchronous. This may be accomplished with code similar to the following:
```c++
// foo_client.h:
#include "chromeos/dbus/method_call_status.h"
class FooClient {
public:
...
// Calls the daemon's GetSomeValue D-Bus method and asynchronously
// invokes |callback| with the result.
virtual void GetSomeValue(DBusMethodCallback<double> callback) = 0;
...
};
// foo_client.cc:
namespace {
// Handles responses to GetSomeValue method calls.
void OnGetSomeValue(DBusMethodCallback<double> callback,
dbus::Response* response) {
if (!response) {
// CallMethod() will already log an error if the method call fails.
std::move(callback).Run(base::nullopt);
return;
}
double value = 0.0;
dbus::MessageReader reader(response);
if (!reader.PopDouble(&value)) {
LOG(ERROR) << "Error reading response: " << response->ToString();
std::move(callback).Run(base::nullopt);
return;
}
std::move(callback).Run(value);
}
} // namespace
...
void FooClientImpl::GetSomeValue(DBusMethodCallback<int> callback) override {
dbus::MethodCall method_call(foo::kFooInterface, foo::kGetSomeValueMethod);
foo_proxy_->CallMethod(&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
base::BindOnce(&OnGetSomeValue, std::move(callback)));
}
```
When writing fake implementations of methods that receive and run callbacks,
post the callback to the message loop instead of running it synchronously. This
matches the calling pattern used in the real implementation, where replies from
system daemons are received asynchronously.
### Don't call services before they're available
Chrome is started in parallel with many system daemons, and your code may run
before the daemon it wants to communicate with has exported its methods or taken
ownership of its service name. If you immediately try to make a method call to
the daemon, it's likely to fail and log annoying, unactionable errors to
Chrome's log file. To avoid this unsightly gaffe, add a
`WaitForServiceToBeAvailable` method to the `Client` interface that callers can
use to defer their method calls until the daemon is ready. See some of the
existing `Client` classes, e.g. `chromeos::CryptohomeClient` or
`chromeos::DebugDaemonClient, for examples of this.
## Sharing state between processes
If you have state shared between Chrome and a system daemon, you'll need to
think about how you'll get both processes back into sync when one of them
restarts or when one of them starts before the other is ready.
### System daemon restarts
While it's usually not expected, the system daemon that you're communicating
with may restart while the system is running. This can happen if the daemon
crashes, of course, but it can also happen when a developer pushes a new version
of the daemon to their development device. To handle this case, you can call
`dbus::ObjectProxy::SetNameOwnerChangedCallback` in your `ClientImpl` class and
notify observers when you see a non-empty "new owner" for the daemon's service
name. See `chromeos::PowerManagerClient` for an example of this.
### Chrome restarts
Chrome restarts are more common. Crashes happen, but Chrome is also
intentionally restarted when a user logs out and sometimes even when a user logs
in (to pick up changes to `chrome://flags`). System daemons can use the same
`SetNameOwnerChangedCallback` technique described above to learn about Chrome
restarts (using the relevant Chrome D-Bus service name).
If Chrome tracks your daemon's state by observing its D-Bus signals, you may
need to provide a method that Chrome can call when it starts or restarts to get
the correct initial state from the daemon.
[D-Bus]: https://www.freedesktop.org/wiki/Software/dbus/
[D-Bus Best Practices]: dbus_best_practices.md
[system_api]: https://chromium.googlesource.com/chromiumos/platform2/+/HEAD/system_api
[src/DEPS]: https://chromium.googlesource.com/chromium/src/+/HEAD/DEPS
[mus+ash]: https://www.chromium.org/developers/mus-ash
[chromeos::CrosDBusService::ServiceProviderInterface]: https://chromium.googlesource.com/chromium/src/+/HEAD/chromeos/dbus/services/cros_dbus_service.h
[ash/dbus]: https://chromium.googlesource.com/chromium/src/+/HEAD/ash/dbus/
[ash_dbus_services.cc]: https://chromium.googlesource.com/chromium/src/+/HEAD/ash/dbus/ash_dbus_services.cc
[chrome/browser/chromeos/dbus]: https://chromium.googlesource.com/chromium/src/+/HEAD/chrome/browser/chromeos/dbus/
[chrome_browser_main_chromeos.cc]: https://chromium.googlesource.com/chromium/src/+/HEAD/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
[chromeos/dbus/services]: https://chromium.googlesource.com/chromium/src/+/HEAD/chromeos/dbus/services/
[ash/BUILD.gn]: https://chromium.googlesource.com/chromium/src/+/HEAD/ash/BUILD.gn
[chrome/browser/chromeos/BUILD.gn]: https://chromium.googlesource.com/chromium/src/+/HEAD/chrome/browser/chromeos/BUILD.gn
[chromeos/dbus]: https://chromium.googlesource.com/chromium/src/+/HEAD/chromeos/dbus