Geolocation unit tests for WebKit using GeoClue

As you probably know, the WebKit project is focused on the implementation of  an open source web browser engine. Support for the W3C Geolocation API is available since 2008 and GeoClue was the selected choice for the WebKitGtk+ port implementation.

Since Igalia is very interested on the WebKit project, I’ve got the chance to devote some time  to explore the integration of GeoClue in WebKit and learning more about this project, which will be the main bet for our Innovation area. It’s really great to leave aside for a while my regular tasks and continue learning and deeper analyzing such an interesting project.

The current state of the GeoClue based implementation it’s somehow preliminary. Basic location services are provided by GeoClue, but the integration with the WebKit core is not fully covered and unit tests are most of them disabled at this moment.

So, I thought it would be a good challenge to complete the implementation and check the unit tests cases to see if I’m able to fix at least one of them. I think it would be a good start 🙂

There are 24 unit test cases defined and only 4 of them are enabled at this moment and they are basically just testing if GeoClue is installed and configured, or checking the input arguments type. I’ve thought that working on the enabled.html test would be interesting, because is very simple and it probes the GeoClue API is correctly used and it works as expected.

description(“Tests Geolocation success callback using the mock service.”);

var mockLatitude = 51.478;
var mockLongitude = -0.166;
var mockAccuracy = 100;

if (window.layoutTestController) {
layoutTestController.setGeolocationPermission(true);
layoutTestController.setMockGeolocationPosition(mockLatitude,
mockLongitude,
mockAccuracy);
} else
debug(‘This test can not be run without the LayoutTestController’);

var position;
navigator.geolocation.getCurrentPosition(function(p) {
position = p;
shouldBe(‘position.coords.latitude’, ‘mockLatitude’);
shouldBe(‘position.coords.longitude’, ‘mockLongitude’);
shouldBe(‘position.coords.accuracy’, ‘mockAccuracy’);
finishJSTest();
}, function(e) {
testFailed(‘Error callback invoked unexpectedly’);
finishJSTest();
});

window.jsTestIsAsync = true;
window.successfullyParsed = true;

So, the fist issue to face is the implementation of the setMockGeolocationPosition method, which is unimplemented (see bug 28624) in the Gtk port (LayoutTestControllerGtk.cpp). This method should set the mock position to be retrieved by the Geolocation API method.

The problem is that GeoClue has not such method, at least, as API method. Depending on the location provider selected its possible to establish  a dummy position through the DBus API.

Another problem is that the Master provider, the one used for the WebKitGtk+ port to implement the Geolocation API, has not a very good provider selection algorithm so one of the web services based provider is selected, causing the unit test to become stalled, waiting for a web response which never come.

Hence, it seemed that the work should start at the GeoClue side, talking to the community to look for the proper approach, discussing about the patches I’ve implemented and eventually push those patches to be committed. After some weeks of hard work, the patches are already at the GeoClue bugzilla; lets see how the discussion evolves.

Meanwhile, I started the WebKit tasks implementing the mock operation. The first approach, perhaps the easiest one, would be to directly use the GeoClue API for setting the mock position. using my own GeoClue branch with my patches applied, I was able to correctly execute the success.html unit test. The patch was not too complex, but it required a new dependency in the WebKitTools module, in order to use the GeoClue API from the LayoutTestControllerGtk component.

void LayoutTestController::setMockGeolocationPosition(double latitude, double longitude, double accuracy)
{
// FIXME: Implement for Geolocation layout tests.
// See https://bugs.webkit.org/show_bug.cgi?id=28264.
GeocluePosition *pos = NULL;
const char *service = “org.freedesktop.Geoclue.Providers.Manual”;
const char *path = “/org/freedesktop/Geoclue/Providers/Manual”;
const char *iface = “org.freedesktop.Geoclue.Manual”;
GError *error = NULL;

GeoclueMaster* master = geoclue_master_get_default();
GeoclueMasterClient* client = geoclue_master_create_client(master, 0, 0);
if (geoclue_master_client_set_requirements(client, GEOCLUE_ACCURACY_LEVEL_LOCALITY, 0,
false, GEOCLUE_RESOURCE_ALL, &error)) {

pos = geoclue_master_client_create_iface_position (client, service, path, iface, &error);
geoclue_position_set_position (pos, accuracy, longitude, latitude, 0, &error);
g_object_unref (pos);
}

g_object_unref(master);
}

In spite of being functionally correct, after talking with some of the WebKitGtk+ developers, it seems that might be not the best approach to follow. The Chromium port has solved the problem by implementing its own Location Cache system inside the WebKit code, delegating on the specific Geolocation tools only when no valid location is available. The mock method just set the mock position into this cache, so the unit tests don’t need any external dependency.

Next steps would be talking to the WebkitGtk+ team to evaluate my proposal and figuring out the best approach to follow, probably something similar to what Chromium have implemented.

GeoClue: Analysis and Architecture

After the first post introducing this cool project is time to go further and deeply analyze the GeoClue internals and general behavior, describing briefly he most relevant components of its architecture.

The first thing I noticed during the analysis of this piece of software is that is a quite complex component, at least, from the architecture design point of view. It provides a set of interfaces and DBus bindings to provide a very general and flexible tool for handling and extending location providers implementations. The following picture illustrate this idea:

Lets start analyzing the architecture, exploring each module and the relationship and interactions between the internal components.

Interfaces

GeoClue provides several interfaces to expose the different locations services and configuration operations. The following interfaces are currently defined:

  • GcIfaceGeoclue: Interface for administrative and configuration operations.
  • GcIfaceAddress: Interface for address acquiring operations.
  • GcIfacePosition: Interface for global positioning operations.
  • GcIfaceGeocode: Interface for geocoding  operations.
  • GcIfaceReverseGeocode: Interface for reverse-geocoding  operations.
  • GcIfaceVeolcity: Interface for velocity monitoring operations.

Some of those interfaces are exposed through DBus interfaces. An XML file define the structure of the DBus bindings. The *Glue generated classes are created from those specification files.

Location Providers

The most direct way to obtain the location is through the specific Location Providers implementation, each one using a different strategy for acquiring the data. There are several implementations for both, Position and Address providers, but lets analyze one simple for the time being, say Localnet.

Every Location Provider is defined through the following configuration files:

  • geoclue-localnet.xml: This file defines the exposed DBus methods and signals. The associated *Glue classes are generated from this file.
  • geoclue-localnet.provider: This file defines the settings of the Location Providers, like description, Dbus specification (path, service, iface), accuracy and some special features provided by the provider (e.g. automatic updates).
  • org.freedesktop.Geoclue.providers.Localnet: DBus service launcher file.

The Location Provider class, in this case called GeoclueLocalnet, should inherit from the GcProvider abstract class, which implements the GcIfaceGeoclue interface. It also should implement the abstract methods defined in the corresponding generated *Glue class, which allows the provider to receive calls through the DBus system or session bus.

Location Data Containers

In order to retrieve different kind of Location data there are several classes defined for that purpose, all of them derived classes from GeoclueProvider. This class holds a DBus proxy object to the instance which actually implement the Location mechanism. The specific provider container instantiated, GeoclueAddress for instance, should receive the DBus service specification (path, service); in the case of the Localnet example, it would be  org.freedesktop.Geoclue.Providers.Localent and /org/freedesktop/Geoclue/Providers/Localnet.

Master Provider

The Master provider is designed by a client/server structure, where the server holds a reference to a DBus proxy object to the selected Location provider. The client will evaluate the available providers choosing the best one, following a provider selection algorithm and based on the user requirements.

The Master server could attend several clients and it monitors the currently selected Location Provider, notifying all the clients any status transition and events,  or even the change of the selected provider.

Both components, client and server, are accessed through the DBus interface, org.freedesktop.Geolcue.MasterClient and org.freedesktop.Geolcue.Master interfaces respectively.

Master client/server

As commented before, the Master Provider has a client/server based design; the server is defined as a singleton of the class GeoclueMaster and its the responsible of the clients instantiation.

The GeoclueMasterClient class instances are the ones used for external applications to interact with the Location framework. It creates the necessary providers (Address, Position, Velocity, …) associated to the Master DBus interface (org,freedesktop,Geoclue.MasterClient. The corresponding GcMasterClient instance will receive all the requests, forwarded thought the Master Dbus interface (org.freedesktop.Geoclue.Master) to the Master server, which will derive the call to the selected Location Provider, using the specific provider DBus interface (org.freedesktop.Geoclue.Providers.Localnet).

And thats all; there are other components, like the connectivity manager and the related interfaces, or the web services supporting classes, but i think the contents described in this post are far enough to understand how GeoClue works, or at least, to show what a complex structure it has.

The following sequence diagram could help to understand how the classes interact to get the address, for instance:

A more interesting debate would be if such a complex design is necessary, or if the advantages it provides, in terms of flexibility and generalization, are enough to justify that, or even they could be obtained as a result of a different design, perhaps simpler. But such an interesting debate will take place in future posts 😉 stay tunned.