diff --git a/dom/interfaces/geolocation/Makefile.in b/dom/interfaces/geolocation/Makefile.in index 829495bfc7f9..4c9657330bca 100644 --- a/dom/interfaces/geolocation/Makefile.in +++ b/dom/interfaces/geolocation/Makefile.in @@ -22,6 +22,7 @@ XPIDLSRCS = \ nsIDOMGeoPositionError.idl \ nsIDOMGeoPositionErrorCallback.idl \ nsIDOMNavigatorGeolocation.idl \ + nsIGeolocation.idl \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/dom/interfaces/geolocation/nsIGeolocation.idl b/dom/interfaces/geolocation/nsIGeolocation.idl new file mode 100644 index 000000000000..02e7d84f42be --- /dev/null +++ b/dom/interfaces/geolocation/nsIGeolocation.idl @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "domstubs.idl" + +interface nsIDOMGeoPositionCallback; +interface nsIDOMGeoPositionErrorCallback; +[ptr] native GeoPositionOptions(mozilla::dom::GeoPositionOptions); + +%{C++ +namespace mozilla { +namespace dom { +class GeoPositionOptions; +} +} +%} + +[scriptable, builtinclass, uuid(d8e6449f-92c8-4c6a-aa9f-fef70157ec29)] +interface nsIGeolocation : nsISupports +{ + // Versions of the DOM APIs that don't require JS option values + int32_t watchPosition(in nsIDOMGeoPositionCallback callback, + in nsIDOMGeoPositionErrorCallback errorCallback, + in GeoPositionOptions options); + void getCurrentPosition(in nsIDOMGeoPositionCallback callback, + in nsIDOMGeoPositionErrorCallback errorCallback, + in GeoPositionOptions options); +}; diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index cd4399a53465..0f10a7f8e6a9 100644 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -125,6 +125,7 @@ using namespace mozilla::dom::indexedDB; using namespace mozilla::dom::power; using namespace mozilla::dom::sms; using namespace mozilla::hal; +using namespace mozilla::idl; using namespace mozilla::ipc; using namespace mozilla::layers; using namespace mozilla::net; @@ -2283,8 +2284,24 @@ ContentParent::RecvFilePathUpdateNotify(const nsString& aType, const nsString& a return true; } +static int32_t +AddGeolocationListener(nsIDOMGeoPositionCallback* watcher, bool highAccuracy) +{ + nsCOMPtr geo = do_GetService("@mozilla.org/geolocation;1"); + if (!geo) { + return -1; + } + + GeoPositionOptions* options = new GeoPositionOptions(); + options->enableHighAccuracy = highAccuracy; + int32_t retval = 1; + geo->WatchPosition(watcher, nullptr, options, &retval); + return retval; +} + bool -ContentParent::RecvAddGeolocationListener(const IPC::Principal& aPrincipal) +ContentParent::RecvAddGeolocationListener(const IPC::Principal& aPrincipal, + const bool& aHighAccuracy) { #ifdef MOZ_PERMISSIONS if (Preferences::GetBool("geo.testing.ignore_ipc_principal", false) == false) { @@ -2336,17 +2353,7 @@ ContentParent::RecvAddGeolocationListener(const IPC::Principal& aPrincipal) // To ensure no geolocation updates are skipped, we always force the // creation of a new listener. RecvRemoveGeolocationListener(); - - nsCOMPtr geo = do_GetService("@mozilla.org/geolocation;1"); - if (!geo) { - return true; - } - - nsRefPtr geosvc = static_cast(geo.get()); - nsAutoPtr options(new mozilla::idl::GeoPositionOptions()); - jsval null = JS::NullValue(); - options->Init(nullptr, &null); - geosvc->WatchPosition(this, nullptr, options.forget(), &mGeolocationWatchID); + mGeolocationWatchID = AddGeolocationListener(this, aHighAccuracy); return true; } @@ -2367,12 +2374,13 @@ ContentParent::RecvRemoveGeolocationListener() bool ContentParent::RecvSetGeolocationHigherAccuracy(const bool& aEnable) { - nsRefPtr geoSvc = - nsGeolocationService::GetGeolocationService(); - if (geoSvc) { - geoSvc->SetHigherAccuracy(aEnable); - } - return true; + // This should never be called without a listener already present, + // so this check allows us to forgo securing privileges. + if (mGeolocationWatchID != -1) { + RecvRemoveGeolocationListener(); + mGeolocationWatchID = AddGeolocationListener(this, aEnable); + } + return true; } NS_IMETHODIMP diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index 3f18b580e50c..3d1ba042d157 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -339,7 +339,8 @@ private: const nsString& aFilePath, const nsCString& aReason); - virtual bool RecvAddGeolocationListener(const IPC::Principal& aPrincipal); + virtual bool RecvAddGeolocationListener(const IPC::Principal& aPrincipal, + const bool& aHighAccuracy); virtual bool RecvRemoveGeolocationListener(); virtual bool RecvSetGeolocationHigherAccuracy(const bool& aEnable); diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index 76ca54633c49..ba51204c1ab3 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -430,7 +430,7 @@ parent: nsCString aContentDisposition, bool aForceSave, int64_t aContentLength, OptionalURIParams aReferrer); - AddGeolocationListener(Principal principal); + AddGeolocationListener(Principal principal, bool highAccuracy); RemoveGeolocationListener(); SetGeolocationHigherAccuracy(bool enable); diff --git a/dom/src/geolocation/Makefile.in b/dom/src/geolocation/Makefile.in index c5fe2bce7223..8f35d3e44c36 100644 --- a/dom/src/geolocation/Makefile.in +++ b/dom/src/geolocation/Makefile.in @@ -17,7 +17,6 @@ LIBXUL_LIBRARY = 1 FORCE_STATIC_LIB = 1 FAIL_ON_WARNINGS := 1 - CPPSRCS = \ nsGeolocation.cpp \ nsGeoPosition.cpp \ diff --git a/dom/src/geolocation/nsGeolocation.cpp b/dom/src/geolocation/nsGeolocation.cpp index 7dba3603db84..a34a35b9ed8b 100644 --- a/dom/src/geolocation/nsGeolocation.cpp +++ b/dom/src/geolocation/nsGeolocation.cpp @@ -459,10 +459,8 @@ nsGeolocationRequest::Allow() if (mOptions->maximumAge >= 0) { maximumAge = mOptions->maximumAge; } - if (mOptions->enableHighAccuracy) { - gs->SetHigherAccuracy(true); - } } + gs->SetHigherAccuracy(mOptions && mOptions->enableHighAccuracy); if (lastPosition && maximumAge > 0 && ( PRTime(PR_Now() / PR_USEC_PER_MSEC) - maximumAge <= @@ -588,13 +586,6 @@ nsGeolocationRequest::Update(nsIDOMGeoPosition* aPosition, bool aIsBetter) void nsGeolocationRequest::Shutdown() { - if (mOptions && mOptions->enableHighAccuracy) { - nsRefPtr gs = nsGeolocationService::GetGeolocationService(); - if (gs) { - gs->SetHigherAccuracy(false); - } - } - if (mTimeoutTimer) { mTimeoutTimer->Cancel(); mTimeoutTimer = nullptr; @@ -602,6 +593,15 @@ nsGeolocationRequest::Shutdown() mCleared = true; mCallback = nullptr; mErrorCallback = nullptr; + + // This should happen last, to ensure that this request isn't taken into consideration + // when deciding whether existing requests still require high accuracy. + if (mOptions && mOptions->enableHighAccuracy) { + nsRefPtr gs = nsGeolocationService::GetGeolocationService(); + if (gs) { + gs->SetHigherAccuracy(false); + } + } } bool nsGeolocationRequest::Recv__delete__(const bool& allow) @@ -979,7 +979,8 @@ nsGeolocationService::StartDevice(nsIPrincipal *aPrincipal, bool aRequestPrivate if (XRE_GetProcessType() == GeckoProcessType_Content) { ContentChild* cpc = ContentChild::GetSingleton(); - cpc->SendAddGeolocationListener(IPC::Principal(aPrincipal)); + cpc->SendAddGeolocationListener(IPC::Principal(aPrincipal), + HighAccuracyRequested()); return NS_OK; } @@ -1014,28 +1015,41 @@ nsGeolocationService::SetDisconnectTimer() nsITimer::TYPE_ONE_SHOT); } +bool +nsGeolocationService::HighAccuracyRequested() +{ + for (uint32_t i = 0; i < mGeolocators.Length(); i++) { + if (mGeolocators[i]->HighAccuracyRequested()) { + return true; + } + } + return false; +} + void nsGeolocationService::SetHigherAccuracy(bool aEnable) { + bool highRequired = aEnable || HighAccuracyRequested(); + if (XRE_GetProcessType() == GeckoProcessType_Content) { ContentChild* cpc = ContentChild::GetSingleton(); - cpc->SendSetGeolocationHigherAccuracy(aEnable); + cpc->SendSetGeolocationHigherAccuracy(highRequired); return; } - if (!mHigherAccuracy && aEnable) { + if (!mHigherAccuracy && highRequired) { for (int32_t i = 0; i < mProviders.Count(); i++) { mProviders[i]->SetHighAccuracy(true); } } - if (mHigherAccuracy && !aEnable) { + if (mHigherAccuracy && !highRequired) { for (int32_t i = 0; i < mProviders.Count(); i++) { mProviders[i]->SetHighAccuracy(false); } } - mHigherAccuracy = aEnable; + mHigherAccuracy = highRequired; } void @@ -1106,6 +1120,7 @@ DOMCI_DATA(GeoGeolocation, nsGeolocation) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGeolocation) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMGeoGeolocation) NS_INTERFACE_MAP_ENTRY(nsIDOMGeoGeolocation) + NS_INTERFACE_MAP_ENTRY(nsIGeolocation) NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(GeoGeolocation) NS_INTERFACE_MAP_END @@ -1210,6 +1225,26 @@ nsGeolocation::HasActiveCallbacks() return mPendingCallbacks.Length() != 0; } +bool +nsGeolocation::HighAccuracyRequested() +{ + for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) { + if (mWatchingCallbacks[i]->IsActive() && + mWatchingCallbacks[i]->WantsHighAccuracy()) { + return true; + } + } + + for (uint32_t i = 0; i < mPendingCallbacks.Length(); i++) { + if (mPendingCallbacks[i]->IsActive() && + mPendingCallbacks[i]->WantsHighAccuracy()) { + return true; + } + } + + return false; +} + void nsGeolocation::RemoveRequest(nsGeolocationRequest* aRequest) { @@ -1256,7 +1291,7 @@ nsGeolocation::GetCurrentPosition(nsIDOMGeoPositionCallback *callback, return GetCurrentPosition(callback, errorCallback, options.forget()); } -nsresult +NS_IMETHODIMP nsGeolocation::GetCurrentPosition(nsIDOMGeoPositionCallback *callback, nsIDOMGeoPositionErrorCallback *errorCallback, mozilla::idl::GeoPositionOptions *options) @@ -1329,7 +1364,7 @@ nsGeolocation::WatchPosition(nsIDOMGeoPositionCallback *callback, return WatchPosition(callback, errorCallback, options.forget(), _retval); } -nsresult +NS_IMETHODIMP nsGeolocation::WatchPosition(nsIDOMGeoPositionCallback *callback, nsIDOMGeoPositionErrorCallback *errorCallback, mozilla::idl::GeoPositionOptions *options, diff --git a/dom/src/geolocation/nsGeolocation.h b/dom/src/geolocation/nsGeolocation.h index 902c6ca7c7fd..66851a940373 100644 --- a/dom/src/geolocation/nsGeolocation.h +++ b/dom/src/geolocation/nsGeolocation.h @@ -26,6 +26,7 @@ #include "nsIDOMGeoPositionCallback.h" #include "nsIDOMGeoPositionErrorCallback.h" #include "nsIDOMNavigatorGeolocation.h" +#include "nsIGeolocation.h" #include "nsPIDOMWindow.h" @@ -64,6 +65,7 @@ class nsGeolocationRequest void SendLocation(nsIDOMGeoPosition* location); void MarkCleared(); + bool WantsHighAccuracy() {return mOptions && mOptions->enableHighAccuracy;} bool IsActive() {return !mCleared;} bool Allowed() {return mAllowed;} void SetTimeoutTimer(); @@ -135,6 +137,7 @@ public: // request higher accuracy, if possible void SetHigherAccuracy(bool aEnable); + bool HighAccuracyRequested(); private: @@ -164,14 +167,16 @@ private: /** * Can return a geolocation info */ -class nsGeolocation MOZ_FINAL : public nsIDOMGeoGeolocation +class nsGeolocation MOZ_FINAL : public nsIDOMGeoGeolocation, + public nsIGeolocation { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_NSIDOMGEOGEOLOCATION + NS_DECL_NSIGEOLOCATION - NS_DECL_CYCLE_COLLECTION_CLASS(nsGeolocation) + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsGeolocation, nsIDOMGeoGeolocation) nsGeolocation(); @@ -198,17 +203,12 @@ public: // Check to see if the widnow still exists bool WindowOwnerStillExists(); + // Check to see if any active request requires high accuracy + bool HighAccuracyRequested(); + // Notification from the service: void ServiceReady(); - // Versions of the DOM APIs that don't require JS option values - nsresult WatchPosition(nsIDOMGeoPositionCallback *callback, - nsIDOMGeoPositionErrorCallback *errorCallback, - mozilla::idl::GeoPositionOptions *options, - int32_t *_retval); - nsresult GetCurrentPosition(nsIDOMGeoPositionCallback *callback, - nsIDOMGeoPositionErrorCallback *errorCallback, - mozilla::idl::GeoPositionOptions *options); private: ~nsGeolocation(); diff --git a/dom/tests/unit/test_geo_provider_accuracy.js b/dom/tests/unit/test_geo_provider_accuracy.js new file mode 100644 index 000000000000..068ec3c58c6e --- /dev/null +++ b/dom/tests/unit/test_geo_provider_accuracy.js @@ -0,0 +1,81 @@ +const Cc = Components.classes; +const Ci = Components.interfaces; + +const providerCID = Components.ID("{14aa4b81-e266-45cb-88f8-89595dece114}"); +const providerContract = "@mozilla.org/geolocation/unittest/geoprovider;1"; + +const categoryName = "geolocation-provider"; + +var provider = { + QueryInterface: function eventsink_qi(iid) { + if (iid.equals(Components.interfaces.nsISupports) || + iid.equals(Components.interfaces.nsIFactory) || + iid.equals(Components.interfaces.nsIGeolocationProvider)) + return this; + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + createInstance: function eventsink_ci(outer, iid) { + if (outer) + throw Components.results.NS_ERROR_NO_AGGREGATION; + return this.QueryInterface(iid); + }, + lockFactory: function eventsink_lockf(lock) { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + startup: function() { + }, + watch: function(callback, isPrivate) { + do_execute_soon(function() { + callback.update({coords: {latitude: 42, longitude: 42}, timestamp: 0}); + }); + }, + shutdown: function() { + }, + setHighAccuracy: function(enable) { + if (enable) { + this._seenHigh = true; + } else { + this._seenNotHigh = true; + } + }, + _seenHigh: false, + _seenNotHigh: false +}; + +let runningInParent = true; +try { + runningInParent = Components.classes["@mozilla.org/xre/runtime;1"]. + getService(Components.interfaces.nsIXULRuntime).processType + == Components.interfaces.nsIXULRuntime.PROCESS_TYPE_DEFAULT; +} +catch (e) { } + +var geolocation; +function run_test() +{ + do_test_pending(); + + if (runningInParent) { + Components.manager.nsIComponentRegistrar.registerFactory(providerCID, + "Unit test geo provider", providerContract, provider); + var catMan = Components.classes["@mozilla.org/categorymanager;1"] + .getService(Components.interfaces.nsICategoryManager); + catMan.nsICategoryManager.addCategoryEntry(categoryName, "unit test", + providerContract, false, true); + + var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); + prefs.setBoolPref("geo.testing.ignore_ipc_principal", true); + prefs.setBoolPref("geo.wifi.scan", false); + } + + geolocation = Cc["@mozilla.org/geolocation;1"].createInstance(Ci.nsIDOMGeoGeolocation); + geolocation.getCurrentPosition(function() { + geolocation.getCurrentPosition(function() { + if (runningInParent) { + do_check_true(provider._seenNotHigh); + do_check_true(provider._seenHigh); + } + do_test_finished(); + }, null, {enableHighAccuracy: false, maxAge: 0}); + }, null, {enableHighAccuracy: true, maxAge: 0}); +} \ No newline at end of file diff --git a/dom/tests/unit/test_geo_provider_accuracy_wrap.js b/dom/tests/unit/test_geo_provider_accuracy_wrap.js new file mode 100644 index 000000000000..84ff4def2774 --- /dev/null +++ b/dom/tests/unit/test_geo_provider_accuracy_wrap.js @@ -0,0 +1,62 @@ +const Cc = Components.classes; +const Ci = Components.interfaces; + +const providerCID = Components.ID("{14aa4b81-e266-45cb-88f8-89595dece114}"); +const providerContract = "@mozilla.org/geolocation/unittest/geoprovider;1"; + +const categoryName = "geolocation-provider"; + +var provider = { + QueryInterface: function eventsink_qi(iid) { + if (iid.equals(Components.interfaces.nsISupports) || + iid.equals(Components.interfaces.nsIFactory) || + iid.equals(Components.interfaces.nsIGeolocationProvider)) + return this; + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + createInstance: function eventsink_ci(outer, iid) { + if (outer) + throw Components.results.NS_ERROR_NO_AGGREGATION; + return this.QueryInterface(iid); + }, + lockFactory: function eventsink_lockf(lock) { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + startup: function() { + }, + watch: function(callback, isPrivate) { + do_execute_soon(function() { + callback.update({coords: {latitude: 42, longitude: 42}, timestamp: 0}); + }); + }, + shutdown: function() { + }, + setHighAccuracy: function(enable) { + if (enable) { + this._seenHigh = true; + } else { + this._seenNotHigh = true; + } + }, + _seenHigh: false, + _seenNotHigh: false +}; + +function run_test() +{ + Components.manager.nsIComponentRegistrar.registerFactory(providerCID, + "Unit test geo provider", providerContract, provider); + var catMan = Components.classes["@mozilla.org/categorymanager;1"] + .getService(Components.interfaces.nsICategoryManager); + catMan.nsICategoryManager.addCategoryEntry(categoryName, "unit test", + providerContract, false, true); + + var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); + prefs.setBoolPref("geo.testing.ignore_ipc_principal", true); + prefs.setBoolPref("geo.wifi.scan", false); + run_test_in_child("test_geo_provider_accuracy.js", function() { + do_check_true(provider._seenNotHigh); + do_check_true(provider._seenHigh); + do_test_finished(); + }); +} \ No newline at end of file diff --git a/dom/tests/unit/test_geolocation_provider.js b/dom/tests/unit/test_geolocation_provider.js index 3f32de01a5ec..4dc16685b1a5 100644 --- a/dom/tests/unit/test_geolocation_provider.js +++ b/dom/tests/unit/test_geolocation_provider.js @@ -75,6 +75,7 @@ function run_test() var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); prefs.setCharPref("geo.wifi.uri", "http://localhost:4444/geo"); prefs.setBoolPref("geo.testing.ignore_ipc_principal", true); + prefs.setBoolPref("geo.wifi.scan", false); var obs = Cc["@mozilla.org/observer-service;1"].getService(); obs = obs.QueryInterface(Ci.nsIObserverService); diff --git a/dom/tests/unit/xpcshell.ini b/dom/tests/unit/xpcshell.ini index 8103289d877f..a2c20f706335 100644 --- a/dom/tests/unit/xpcshell.ini +++ b/dom/tests/unit/xpcshell.ini @@ -10,6 +10,9 @@ skip-if = os == "android" [test_geolocation_timeout.js] [test_geolocation_timeout_wrap.js] skip-if = os == "mac" +[test_geo_provider_accuracy.js] +[test_geo_provider_accuracy_wrap.js] +skip-if = os == "mac" [test_multiple_geo_listeners.js] [test_multiple_geo_listeners_wrap.js] skip-if = os == "mac"