Implementing APIs in Chromium*

By Srinivasa Ragavan Venkateswaran

Chromium has a multi-process architecture. There are separate processes for browser tabs to protect the overall application from bugs and glitches in the rendering engine. Access is restricted between the rendering engine process to others and to the rest of the system. In some ways, this brings to web browsing the benefits that memory protection and access control brought to operating systems.

[Image source: http://www.chromium.org/developers/design-documents/multi-process-architecture/arch.png]

Every tab is represented by a RenderProcess for which the browser has a RenderProcessHost for managing each tab. Renderer uses Blink for interpreting and laying out HTML. Web APIs can be written by providing new interfaces or by extending existing interfaces like Navigator, Document, etc. The actual concepts can be explained using the context of a sample implementation, like Battery Status API to make it more clear. Let’s now consider an example of how to extend the web APIs for Battery Status and see what we do here.

Battery status is provided by extending the Navigator interface. It is extended by adding the following in the IDL file.

//NavigatorBattery.idl
partial interface Navigator {
    readonly attribute BatteryManager battery;
};

And we need to create a BatteryManager object.

//NavigatorBattery.cpp
BatteryManager* NavigatorBattery::battery(Navigator& navigator)
{
    m_batteryManager = BatteryManager::create(navigator.frame()->document());
    return m_batteryManager.get();
}

This BatteryManager class provides receives updates about the BatteryStatus and sends events to the webpage. The web page can access the battery status like:

//test-battery-status.html
var battery = navigator.battery;
document.getElementById("p1").innerHTML = "Charging Status: " + (battery.charging ? "Charging" : "Discharging");
document.getElementById("p2").innerHTML = "Charging Time: " + battery.chargingTime;
document.getElementById("p3").innerHTML = "Discharging Time: " + battery.dischargingTime;
document.getElementById("p4").innerHTML = "Percentage: " + battery.level*100;

Each Navigator would have a BatteryManager and there is more than one Navigator in the lifecycle. It would be unoptimal for each Battery Manager to access battery information independently. So we would need a class that collects battery information and passes it across to all Battery Manager. Let’s call this class as BatteryDispatcher

//BatteryDispatcher.cc
BatteryDispatcher::updateBatteryStatus(BatteryStatus status) {
        size_t size = m_managers.size();
        for (size_t i = 0; i < size; ++i)
            m_managers[i]->batteryStatusChanged(status);
}

But this BatteryDispatcher class is on Blink and this needs to be accessed from Chromium. Chromium extends few interfaces from Blink like WebView, Platform, etc, in content/renderer/render_view_impl.cc, content/renderer/renderer_webkitplatformsupport_impl.cc respectively. If an API is specific to each page/view it should be extended via the WebView interface and things like BatteryStatus, that aren't specific to pages should be extended via the Platform interface. To extend the Platform interface in Blink add the following:

//public/platform/Platform.h
class Platform {
public:
[...]
virtual void setBatteryDispatcher( );
[...]

This Platform will be implemented by Chromium and will have setBatteryDipatcher implemented there. But Blink should set the right dispatcher.

//BatteryDispatcher.cc
BatteryDispatcher::BatteryDispatcher( ) {
    blink::Platform::current()->setBatteryListener(this);
}

Let’s see how to implement this in Chromium's Platform implementation:

//content/renderer/renderer_webkitplatformsupport_impl.cc
+void RendererWebKitPlatformSupportImpl::setBatteryDispatcher(BatteryDispatcher* dispatcher) {
+    battery_monitor_->setBatteryDispatcher(dispatcher);
+}

BatteryMonitor should have implemention to preserve the dispatcher.

//BatteryMonitor.cc
void BatteryMonitor::setBatteryDispatcher(BatteryDispatcher *dispatcher) {
    dispatcher_.reset(dispatcher);
}

This BatteryMonitor class is the glue class between the BatteryDispatcher on the Blink side and the IPC on the Chromium side. BatteryMonitor uses the IPC to fetch Battery Status and passes it to the Dispatcher. BatteryMonitor should receive an IPC call back whenever there is a new battery status. The IPC can happen between RenderProcess/RenderProcessHost or RenderView/RenderViewHost. View specific APIs should be using the IPC mechanism over RenderView/RenderViewHost and Platform specific APIs should IPC over RenderProcess/RenderProcessHost. More about this can be read at http://www.chromium.org/developers/design-documents/inter-process-communication.

// battery_status_messages.h
#define IPC_MESSAGE_START BatteryStatusMsgStart

IPC_STRUCT_TRAITS_BEGIN(BatteryStatus)
  IPC_STRUCT_TRAITS_MEMBER(charging)
  IPC_STRUCT_TRAITS_MEMBER(battery_seconds_to_empty)
  IPC_STRUCT_TRAITS_MEMBER(battery_seconds_to_full)
  IPC_STRUCT_TRAITS_MEMBER(battery_percentage)
IPC_STRUCT_TRAITS_END()

// Messages sent from the browser to the renderer.

IPC_MESSAGE_CONTROL1(BatteryStatusMsg_Updated,
                    BatteryStatusStatus)

The above code defines the communication on the IPC. Some APIs might add code to call from the renderer to the browser.

//BatteryMonitor.h
class CONTENT_EXPORT BatteryMonitor : public RenderProcessObserver {
[...]
  virtual bool OnControlMessageReceived(const IPC::Message& message) OVERRIDE;
}

//BatteryMonitor.cc
bool BatteryMonitor::OnControlMessageReceived(const IPC::Message& msg) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(BatteryMonitor, msg)
    IPC_MESSAGE_HANDLER(BatteryStatusMsg_Updated, OnBatteryStatusUpdated)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

void BatteryMonitor::OnBatteryStatusUpdated(BatteryStatus status) {
  dispatcher_->updateBatteryStatus(status);
}

Now since we have the implementation ready on the Renderer side, we need to see how to send the battery status via IPC from the Browser process. Within the browser, communication with the renderers is done in a separate I/O thread. Messages to and from the views then have to be proxied over to the main thread using a ChannelProxy. The advantage of this scheme is that resource requests (for web pages, etc.), which are the most common and performance critical messages, can be handled entirely on the I/O thread and not block the user interface. These are done through the use of a ChannelProxy::MessageFilter which is inserted into the channel by the RenderProcessHost. This filter runs in the I/O thread, intercepts resource request messages, and forwards them directly to the resource dispatcher host. See http://dev.chromium.org/developers/design-documents/multi-process-resource-loading for more information on this.

Let’s see how the IPC happens between the Browser process and the Renderers.

[Image Source: http://dev.chromium.org/_/rsrc/1220197833456/developers/design-documents/multi-process-resource-loading/Resource-loading.png]

There is a dedicated MessageFilter object that every API should implement that represents each Renderer and passes information back to the Renderer. Let’s call this BatteryStatusMessageFilter

//BatteryStatusMessageFilter.h
class BatteryStatusMessageFilter : public BrowserMessageFilter {
[...]
}

//BatteryStatusMessageFilter.cc
BatteryStatusMessageFilter::BatteryStatusMessageFilter()
    : BrowserMessageFilter(BatteryStatusMsgStart) {
}

BatteryStatusMessageFilter::batteryStatusChanged(BatteryStatus status) {
    Send(new BatteryStatusMsg_Updated(status));
}

The MessageFilter passes the specific IPC communication that this class is interested in the constructor. The BatteryStatusProvider provides each message filter with status information when there is a change.

//BatteryStatusProvider.cc
void BatteryStatusProvider::SendUpdate(BatteryStatus status) {
    for (int i=0; i<m_messageFilters_.size(); i++)
        m_messageFilters[i]->batteryStatusChanged(status)
}

The BatteryStatusProvider class can be extended in each platform like Windows, Linux, Android etc and each platform should implement its own way for plugging into the lower level system to identify the battery information and call BatteryStatusProvider::SendUpdate(status) with the updated status information so that it is passed down via IPC to each renderer.

Let’s take Linux implementation for example.

// BatteryStatusProviderLinux.h
class BatteryStatusProviderLinux : public BatteryStatusProvider  {

private:
    BatteryStatusProviderLinux();
    virtual ~BatteryStatusProviderLinux();

    //Initialize the Dbus to watch on service: org.freedesktop.UPower
    InitDBus();
    [...]
}


// BatteryStatusProviderLinux.cc
void  BatteryStatusProviderLinux::InitDBus() {
    // Use EnumerateDevices method call on Service: org.freedesktop.Upower
    // to get the list of devices and look for the device with type=2 which
    // would say that the device is a battery power source.
    [...]
    // Once you have the dbus path for the battery object, get the dbus proxy
    // object.
    dbus::ObjectProxy* battery_proxy =
        system_bus_->GetObjectProxy(“org.freedesktop.UPower”,
                                    battery_source_dbus_path_);

    // listen on the object for PropertyChanged signal which would mean that
    // the battery object's property has changed.
    battery_proxy_->ConnectToSignal(
      dbus::kDBusPropertiesInterface,
      dbus::kDBusPropertiesChangedSignal,
      base::Bind(&BatteryStatusProviderLinux::PropertyChanged,
                 weak_ptr_factory_.GetWeakPtr()),
      base::Bind(&BatteryStatusProviderLinux::SignalConnected,
                 weak_ptr_factory_.GetWeakPtr()));
    [...]
}

void BatteryStatusProviderLinux::PropertyChanged(dbus::Signal* signal) {
    BatteryStatus status;

    uint32 state = GetPropertyUint32(battery_proxy_, "State");
    if (state == 1 /* Charging */
        || state == 4 /* Fully Charged */)
      status.charging = true;
    else
      status.charging = false;

    status.time_to_full = GetPropertyInt64 (battery_proxy_, "TimeToFull");
    status.time_to_empty = GetPropertyInt64 (battery_proxy_, "TimeToEmpty");
    status.percentage = GetPropertyDouble (battery_proxy_, "Percentage");

    // This sends the new status to message filters.
    SendUpdate(status);
}
[...]

In Summary, every implementation of the provider will send updates to the list of registered message filters. The message filters in turn pass the status updates via IPC to the renderer’s BatteryMonitor. BatteryMonitor would receive them and in turn pass to the BatteryDispatcher which updates the BatteryManager and thus is web pages could access the battery information.

Similar APIs it could be implemented as such and some variations might be required for extending other APIs that are not of similar nature. For more detailed information, please read http://www.chromium.org/developers/design-documents . It has in-depth details of every approach and concept.

For more complete information about compiler optimizations, see our Optimization Notice.
Tags: