Re: Geofencing alternative proposal

On Fri, Sep 12, 2014 at 1:31 PM, Martin Thomson <martin.thomson@gmail.com>
wrote:

> 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 deliberately left out/didn't address many details in my initial proposal,
since trying to address every little detail in one go didn't seem like a
very productive way to get anywhere. But maybe I should have addressed some
of these details a bit more up front, since it might have made some of my
design choices more clear. I was kind of expecting to get some
feedback/questions which would then give me the opportunity to address
these details, but that unfortunately didn't happen.


> I'm guided here by the discussion here:
> https://github.com/slightlyoff/ServiceWorker/issues/445, which is only
> recent.
>
Yes, I'll shortly send an updated version of my proposal taking that into
account as well.


>
> ## 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.
>
I'm not sure if there is much value in exposing the existing Geolocation
API to service workers? While it might be useful to be able to call
getCurrentPosition from a service worker, I'm not entirely sure what the
use case for that would be (maybe something like a find-my-device service,
where a service worker might want to react to receiving a push message by
posting back the current location?).


> 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.
>
I'm not entirely sure what the purpose is of the
GeolocationWorkerRegistration layer between the ServiceWorkerRegistration
and the GeolocationFenceControl? Is the idea here to use this as an
indicator that the website is asking for background geolocation permissions
(and if that's the intention, would it make sense to have a more explicit
way of asking for this permission?)
The entire Geolocation (and thus GeolocationFenceControl) object is already
exposed to both websites and service workers in your proposal, so this
doesn't seem to add much.


> ## 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.
>
While localizing events like this works perfectly fine for regular
websites, I don't see how this is going to work for service workers. What
happens when a browser tries to deliver a geofence event to a service
worker? Assuming the service worker isn't currently running, the browser
will create a fresh javascript context (in which no GeolocationFence
instances exist), load the service worker, and then won't have anywhere to
actually deliver the event. Generally with service worker related events,
the toplevel javsacript code (which is executed when the service worker is
loaded) registers all the event handlers that will handle events, but this
top-level code won't be able to install event handlers inside a
GeolocationFence instance. Hence why in my proposal I had top-level
geofenceenter, leave etc events in the service worker global scope.

This also is the reason for having the (website specified) "id" associated
with a geofence registration. An API that can only indicate "some geofence
with these (floating point) coordinates and radius was entered" seems like
it would be much harder to use than an API where the website/worker can
associate some piece of data (like this id) with the registration that
would allow it to immediately know which entity is actually represented by
that fence without having to try to match it themselves via the coordinates.

Now it might make sense to additionally have these onenter/onleave events
on a GeolocationFence like object to support the usecase of an website
itself listening to events related to geofences, so I've added that to my
updated proposal I'm about to send out after I'm done writing this email.


> 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.
>
I don't really have opinions yet on the exact way geofences are
defined/instantiated, but then it doesn't really effect the overall shape
of the API, and we seem to mostly agree on the desired features.


> ## 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.
>
I agree that MIN_RADIUS definitely isn't very useful. There might be uses
for MAX_RADIUS, but it should't be too hard to support arbitrarily large
regions even if the underlying platform API doesn't support it, so I can
live with dropping that one as well.


>
> ## 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.
>
This seems essentially the same as my GeofencingEvent (except that in my
proposal the event also contains the actual region, necessitated by the
fact that events have to be fired on some top-level object to be available
in service workers.


>
> ## 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.
>
I agree that some kind of error event will probably be necessary, although
I'm not sure about the exact shape. But some way of indicating that a
geofence is no longer registered might be nice to have. Although ideally it
would never be needed to send this event of course. As long as geofencing
in general is available, the browser should do everything it can to keep
all registrations active, and I'm not really sure under what circumstances
only a subset of geofences would somehow unregister itself.

Received on Tuesday, 16 September 2014 00:04:11 UTC