- From: Martin Thomson <martin.thomson@gmail.com>
- Date: Fri, 12 Sep 2014 13:31:08 -0700
- To: public-geolocation@w3.org
In the spirit of taking until the last minute, here is something that has a little more detail than the other proposals. I've had *some* feedback from Mozilla folks, but I wouldn't say that this is a Mozilla proposal by any stretch. Consider it merely as input to the process. --- I think that Google's proposal includes most of the right elements, but I don't think that it covers enough of the details. On my little deep dive, I found quite a few. I'm basing this proposal off the Google one, but I'm proposing structural changes, as well as several superficial changes. I'm guided here by the discussion here: https://github.com/slightlyoff/ServiceWorker/issues/445, which is only recent. ## Initialization and Access The core of the feature seems about right, but some restructuring might be needed: interface GeolocationFenceControl { Promise<GeolocationFence> addFence(GeolocationFenceDescription fence); Promise<sequence<GeolocationFence>> getFences(); }; Then, we make this available from window.geolocation: [Exposed=(Window, Worker)] // I should have added this annotation throughout, but I'm lazy partial interface Geolocation { readonly attribute GeolocationFenceControl geofence; }; And also make this available from the service worker global scope. partial interface ServiceWorkerGlobalScope { readonly attribute Geolocation geolocation; }; A consequence of this choice is that sites with persistent permissions for location will be able to request location information spontaneously from a SW. I think that's a feature, but it's something we should discuss. There are implications: watchPosition might need to be specifically prohibited from the SW context, and we might want to consider the use of a promise-based API too. But I'll leave those for later discussion, I think. The control object is also made available from the window scope in order to register the intent to use geolocation within a Service Worker. Specifically, to register what regions are of interest to that service worker, if any. partial interface ServiceWorkerRegistration { readonly attribute GeolocationWorkerRegistration geolocation; }; If the outer window hasn't registered for geolocation in the service worker, then the object is there, but it can be rendered non-functional. That is, attempts to register fences are rejected immediately. Note also that persistent user consent will be needed to access geolocation in this context. interface GeolocationWorkerRegistration { Promise<GeolocationFenceControl> register(); // I originally had a variadic argument here as a shortcut for // registration.register().addFence(f); // ...since the return values aren't especially interesting. // But that's pure sugar and unnecessary. }; [ISSUE] The GeolocationFence objects that are returned from this context aren't going to be functional - events can't surface in the window, they will only appear in the worker. I don't know if it is worth creating parallel interfaces for this purpose that don't produce events, or whether we can just neuter those objects (i.e., make the events not fire). There are benefits to either approach. ## GeoFence Object Definition interface GeolocationFence : EventTarget { // cancel() kills the fence (i.e., no further events) Promise<void> cancel(); attribute EventHandler onenter; attribute EventHandler onleave; attribute EventHandler onerror; }; As you can see, this is where you get your events. I've had at least one person suggest that events on the global (or globally accessible singleton) is perfectly fine, but I think that this turns out cleaner. Much if these issues come down to taste, so I'm happy to go with whatever people like best on the whole. Me, I prefer to localize things like this. The GeolocationFence has a dictionary analogue for construction/initialization purposes. dictionary GeolocationFenceDescription { GeolocationFenceType type; }; enum GeolocationFenceType { "circle", "polygon" }; Regarding GeolocationFenceType, we could probably get away with the API inferring type, but that leads to errors. The resulting IDL is clunky, but the usage should be straightforward: var r = global.geolocation.geofence .addRegion({type: "circle", center: { latitude: 43... }, radius: 100}); Alternatively it could be made an extra argument, I'm not sure what is best here: .addRegion("circle", { center: { latitude: 43... }, radius: 100}); [ISSUE] I note that other APIs use DOMString as the input type when enumerations are used to permit extension. I'm not sure if that's a good idea here. ## Region Types I think that we need both circular and polygonal fences. Imagine here that there is a dictionary form for each of these. All of these have no constructor, instead relying on the addRegion return value for construction. interface GeolocationCircularFence : GeolocationFence { readonly attribute GeolocationPoint center; readonly attribute double radius; // metres, always }; interface GeolocationPolygonFence : GeolocationFence { readonly attribute sequence<GeolocationPoint> exterior; }; Imagine dictionary analogues inheriting from GeolocationFenceDescription with the same attributes. Then there is a point type (not needed as a separate type if we don't do polygonal fences). interface GeolocationPoint { readonly attribute double latitude; readonly attribute double longitude; }; This intentionally excludes altitude and the motion-related fields from Position. Regarding the MIN_RADIUS and MAX_RADIUS proposed by Google, I don't see the need to expose that information. There are certainly reasons why certain sizes of region represent implementation challenges, but we should instead surface errors. There are certainly cases where regions will be effectively useless due to being too small, but I don't think that we need to (or indeed can) provide such specific feedback to apps. On the other hand, a large region can always be addressed, even if it is not able to use efficient hardware support. We already need to implement something in the absence of hardware support for this, so I don't see the size being a special blocker. ## Presenting Results The current API uses callbacks, which this would not. So we need a way to expose the information we have in events. That requires a definition. [Constructor(DOMString type, GeolocationEventInit init)] interface GeolocationEvent : Event { readonly attribute Position position; }; // GeolocationEventInit is a dictionary with the same Here, the event exposes the current position. I've marked this as mandatory on the assumption that knowing location is a precondition on being able to provide this event, but Stephen Li's use case around unknown locations would change that. ## Error Handling The other proposals don't really deal with this in detail, but we would need include an error handling in a couple of cases. When the platform is unable to create a new geofencing region I think we can just have the relevant promise rejected. That's easy. We also need to deal with transient errors (can't determine current location and there is a risk that it is near a region boundary). I can't think of many particularly compelling terminal errors; it seems like once a region has been added, we should try to maintain it. We've been discussing having service workers restored at startup, which entails a risk that certain regions can't be re-registered. I think that we want an onerror event for a given region that pops for both transient (location unknown) and permanent (couldn't maintain region registration) errors. That might suggest two handlers, but I'll defer to others with more experience on this point. Here's one option [Constructor(DOMString type, GeolocationErrorEventInit init)] interface GeolocationErrorEvent : Event { readonly attribute unsigned short errorCode; // uses PositionError.code }; // GeolocationErrorEventInit is a dictionary with the same And this relies on PositionError changes: partial interface PositionError { const unsigned short GEOFENCE_UNREGISTERED = 4; }; This code indicates that the corresponding fence is no more.
Received on Friday, 12 September 2014 20:31:40 UTC