1 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
3 const Ci = Components.interfaces;
4 const Cc = Components.classes;
6 var gLoggingEnabled = false;
7 var gTestingEnabled = false;
9 function nowInSeconds()
11 return Date.now() / 1000;
17 aMsg = "*** WIFI GEO: " + aMsg + "\n";
18 Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(aMsg);
23 function WifiGeoAddressObject(streetNumber, street, premises, city, county, region, country, countryCode, postalCode) {
25 this.streetNumber = streetNumber;
27 this.premises = premises;
31 this.country = country;
32 this.countryCode = countryCode;
33 this.postalCode = postalCode;
36 WifiGeoAddressObject.prototype = {
38 QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGeoPositionAddress, Ci.nsIClassInfo]),
40 getInterfaces: function(countRef) {
41 var interfaces = [Ci.nsIDOMGeoPositionAddress, Ci.nsIClassInfo, Ci.nsISupports];
42 countRef.value = interfaces.length;
46 getHelperForLanguage: function(language) null,
47 implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
48 flags: Ci.nsIClassInfo.DOM_OBJECT,
51 function WifiGeoCoordsObject(lat, lon, acc, alt, altacc) {
56 this.altitudeAccuracy = altacc;
59 WifiGeoCoordsObject.prototype = {
61 QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGeoPositionCoords, Ci.nsIClassInfo]),
63 getInterfaces: function(countRef) {
64 var interfaces = [Ci.nsIDOMGeoPositionCoords, Ci.nsIClassInfo, Ci.nsISupports];
65 countRef.value = interfaces.length;
69 getHelperForLanguage: function(language) null,
70 classDescription: "wifi geo position coords object",
71 implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
72 flags: Ci.nsIClassInfo.DOM_OBJECT,
82 function WifiGeoPositionObject(location) {
84 this.coords = new WifiGeoCoordsObject(location.latitude,
86 location.accuracy || 12450, // .5 * circumference of earth.
87 location.altitude || 0,
88 location.altitude_accuracy || 0);
90 if (location.address) {
91 let address = location.address;
92 this.address = new WifiGeoAddressObject(address.street_number || null,
93 address.street || null,
94 address.premises || null,
96 address.county || null,
97 address.region || null,
98 address.country || null,
99 address.country_code || null,
100 address.postal_code || null);
105 this.timestamp = Date.now();
108 WifiGeoPositionObject.prototype = {
110 QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGeoPosition, Ci.nsIClassInfo]),
112 // Class Info is required to be able to pass objects back into the DOM.
113 getInterfaces: function(countRef) {
114 var interfaces = [Ci.nsIDOMGeoPosition, Ci.nsIClassInfo, Ci.nsISupports];
115 countRef.value = interfaces.length;
119 getHelperForLanguage: function(language) null,
120 classDescription: "wifi geo location position object",
121 implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
122 flags: Ci.nsIClassInfo.DOM_OBJECT,
129 // For information about the HELD format, see:
130 // http://tools.ietf.org/html/draft-thomson-geopriv-held-measurements-05
131 HELD.encode = function(requestObject) {
133 var requestString = "<locationRequest xmlns=\"urn:ietf:params:xml:ns:geopriv:held\">";
135 if (requestObject.wifi_towers && requestObject.wifi_towers.length > 0) {
136 requestString += "<measurements xmlns=\"urn:ietf:params:xml:ns:geopriv:lm\">";
137 requestString += "<wifi xmlns=\"urn:ietf:params:xml:ns:geopriv:lm:wifi\">";
138 for (var i=0; i < requestObject.wifi_towers.length; ++i) {
139 requestString += "<neighbourWap>";
140 requestString += "<bssid>" + requestObject.wifi_towers[i].mac_address + "</bssid>";
141 requestString += "<ssid>" + requestObject.wifi_towers[i].ssid + "</ssid>";
142 requestString += "<rssi>" + requestObject.wifi_towers[i].signal_strength + "</rssi>";
143 requestString += "</neighbourWap>";
146 requestString += "</wifi></measurements>";
148 requestString += "</locationRequest>";
149 return requestString;
152 // Decode a HELD response into a Gears-style object
153 HELD.decode = function(responseXML) {
154 // Find a Circle object in PIDF-LO and decode
155 function nsResolver(prefix) {
157 'held': 'urn:ietf:params:xml:ns:geopriv:held',
158 'pres': 'urn:ietf:params:xml:ns:pidf',
159 'gp': 'urn:ietf:params:xml:ns:pidf:geopriv10',
160 'gml': 'http://www.opengis.net/gml',
161 'gs': 'http://www.opengis.net/pidflo/1.0',
163 return ns[prefix] || null;
166 var xpathEval = Components.classes["@mozilla.org/dom/xpath-evaluator;1"].createInstance(Ci.nsIDOMXPathEvaluator);
168 // Grab values out of XML via XPath
169 var pos = xpathEval.evaluate(
170 '/held:locationResponse/pres:presence/pres:tuple/pres:status/gp:geopriv/gp:location-info/gs:Circle/gml:pos',
173 Ci.nsIDOMXPathResult.STRING_TYPE,
176 var rad = xpathEval.evaluate(
177 '/held:locationResponse/pres:presence/pres:tuple/pres:status/gp:geopriv/gp:location-info/gs:Circle/gs:radius',
180 Ci.nsIDOMXPathResult.NUMBER_TYPE,
183 var uom = xpathEval.evaluate(
184 '/held:locationResponse/pres:presence/pres:tuple/pres:status/gp:geopriv/gp:location-info/gs:Circle/gs:radius/@uom',
187 Ci.nsIDOMXPathResult.STRING_TYPE,
190 // Bail if we don't have a valid result (all values && uom==meters)
191 if ((pos.stringValue == null) ||
192 (rad.numberValue == null) ||
193 (uom.stringValue == null) ||
194 (uom.stringValue != "urn:ogc:def:uom:EPSG::9001")) {
198 // Split the pos value into lat/long
199 var coords = pos.stringValue.split(/[ \t\n]+/);
201 // Fill out the object to return:
204 latitude: parseFloat(coords[0]),
205 longitude: parseFloat(coords[1]),
206 accuracy: rad.numberValue
212 function WifiGeoPositionProvider() {
213 this.prefService = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch).QueryInterface(Ci.nsIPrefService);
215 gLoggingEnabled = this.prefService.getBoolPref("geo.wifi.logging.enabled");
219 gTestingEnabled = this.prefService.getBoolPref("geo.wifi.testing");
224 WifiGeoPositionProvider.prototype = {
225 classID: Components.ID("{77DA64D3-7458-4920-9491-86CC9914F904}"),
226 QueryInterface: XPCOMUtils.generateQI([Ci.nsIGeolocationProvider,
228 Ci.nsITimerCallback]),
236 startup: function() {
237 if (this.started == true)
242 LOG("startup called. testing mode is" + gTestingEnabled);
243 // if we don't see anything in 5 seconds, kick of one IP geo lookup.
244 // if we are testing, just hammer this callback so that we are more or less
245 // always sending data. It doesn't matter if we have an access point or not.
246 this.hasSeenWiFi = false;
247 this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
248 if (gTestingEnabled == false)
249 this.timer.initWithCallback(this, 5000, this.timer.TYPE_ONE_SHOT);
251 this.timer.initWithCallback(this, 200, this.timer.TYPE_REPEATING_SLACK);
256 if (!this.wifi_service) {
257 this.wifi_service = Cc["@mozilla.org/wifi/monitor;1"].getService(Components.interfaces.nsIWifiMonitor);
258 this.wifi_service.startWatching(this);
262 shutdown: function() {
263 LOG("shutdown called");
264 if(this.wifi_service)
265 this.wifi_service.stopWatching(this);
266 this.wifi_service = null;
268 if (this.timer != null) {
273 // Although we aren't using cookies, we should err on the side of not
274 // saving any access tokens if the user asked us not to save cookies or
275 // has changed the lifetimePolicy. The access token in these cases is
276 // used and valid for the life of this object (eg. between startup and
278 let prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
279 if (prefBranch.getIntPref("network.cookie.lifetimePolicy") != 0)
280 prefBranch.deleteBranch("geo.wifi.access_token.");
282 this.started = false;
285 getAccessTokenForURL: function(url)
287 // check to see if we have an access token:
288 var accessToken = "";
291 var accessTokenPrefName = "geo.wifi.access_token." + url;
292 accessToken = this.prefService.getCharPref(accessTokenPrefName);
294 // check to see if it has expired
295 var accessTokenDate = this.prefService.getIntPref(accessTokenPrefName + ".time");
297 var accessTokenInterval = 1209600; /* seconds in 2 weeks */
299 accessTokenInterval = this.prefService.getIntPref("geo.wifi.access_token.recycle_interval");
302 if (nowInSeconds() - accessTokenDate > accessTokenInterval)
311 onChange: function(accessPoints) {
313 LOG("onChange called");
314 this.hasSeenWiFi = true;
316 // send our request to a wifi geolocation network provider:
317 var xhr = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
318 .createInstance(Ci.nsIXMLHttpRequest);
320 // This is a background load
321 xhr.mozBackgroundRequest = true;
323 var provider_url = this.prefService.getCharPref("geo.wifi.uri");
324 var provider_protocol = 0;
326 provider_protocol = this.prefService.getIntPref("geo.wifi.protocol");
329 LOG("provider url = " + provider_url);
331 xhr.open("POST", provider_url, false);
333 // set something so that we can strip cookies
334 xhr.channel.loadFlags = Ci.nsIChannel.LOAD_ANONYMOUS;
336 xhr.onerror = function(req) {
337 LOG("onerror: " + req);
340 xhr.onload = function (req) {
342 LOG("xhr onload...");
345 // if we get a bad response, we will throw and never report a location
347 switch (provider_protocol) {
349 LOG("service returned: " + req.target.responseXML);
350 response = HELD.decode(req.target.responseXML);
354 LOG("service returned: " + req.target.responseText);
355 response = JSON.parse(req.target.responseText);
362 // response looks something like:
363 // {"location":{"latitude":51.5090332,"longitude":-0.1212726,"accuracy":150.0},"access_token":"2:jVhRZJ-j6PiRchH_:RGMrR0W1BiwdZs12"}
365 // Check to see if we have a new access token
366 var newAccessToken = response.access_token;
367 if (newAccessToken != undefined)
369 var prefService = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
370 var accessToken = "";
371 var accessTokenPrefName = "geo.wifi.access_token." + req.target.channel.URI.spec;
372 try { accessToken = prefService.getCharPref(accessTokenPrefName); } catch (e) {}
374 if (accessToken != newAccessToken) {
375 // no match, lets cache
376 LOG("New Access Token: " + newAccessToken + "\n" + accessTokenPrefName);
379 prefService.setIntPref(accessTokenPrefName + ".time", nowInSeconds());
380 prefService.setCharPref(accessTokenPrefName, newAccessToken);
382 // XXX temporary hack for bug 575346 to allow geolocation to function
387 if (response.location) {
388 var newLocation = new WifiGeoPositionObject(response.location);
390 var update = Cc["@mozilla.org/geolocation/service;1"].getService(Ci.nsIGeolocationUpdate);
391 update.update(newLocation);
395 var accessToken = this.getAccessTokenForURL(provider_url);
399 request_address: true,
402 if (accessToken != "")
403 request.access_token = accessToken;
405 if (accessPoints != null) {
406 function filterBlankSSIDs(ap) ap.ssid != ""
407 function deconstruct(ap) ({
410 signal_strength: ap.signal
412 request.wifi_towers = accessPoints.filter(filterBlankSSIDs).map(deconstruct);
416 switch (provider_protocol) {
418 requestString = HELD.encode(request);
422 requestString = JSON.stringify(request);
424 LOG("client sending: " + requestString);
427 xhr.send(requestString);
431 onError: function (code) {
432 LOG("wifi error: " + code);
435 notify: function (timer) {
436 if (!gTestingEnabled) {
437 if (this.hasSeenWiFi == false)
442 // if we are testing, we need to hammer this.
448 var NSGetFactory = XPCOMUtils.generateNSGetFactory([WifiGeoPositionProvider]);