gecko-dev/dom/geolocation/nsGeolocation.cpp
Boris Zbarsky 081fa29a04 Bug 1536719. Fix handling of member method calls in the MOZ_CAN_RUN_SCRIPT analysis. r=andi
The old code for member method calls did the following:

 1) Find the member method calls.
 2) Look at their "this" expression.
 3) If the "this" is an operator call, check for any of the arguments of the
    operator call being invalid.
 4) Otherwise (if not an operator call) check for the "this" value being
    invalid.

This wasn't right, because the "is invalid" check checks the type and only
considers refcounted things.  So if the code looked something like
"foo[i]->call_method()", we would look at the types of "foo" and "i" and
determine that none of those are refcounted types so there is nothing invalid
here (since "foo" is some sort of array type and "i" is an integer).  The new
setup just checks whether the "this" value is invalid, which does the type
check on the "this" value itself; in the "foo[i]->call_method()" case on
"foo[i]".  We then adjust the exclusions in InvalidArg to consider operator->
on known-live things valid, to allow the thing that we were really trying to
accomplish with the "check for an operator call" bits:
"stackRefPtr->some_method()".

The test coverage being added for the made-up TArray type is meant to catch
things like the geolocation issue that was being hidden by the buggy behavior.
I'm not using nsTArray itself because some header included by nsTArray.h
tries to define operator new/delete bits inline and that triggers warnings that
then cause a clang-plugin test failure, because they're unexpected.

Differential Revision: https://phabricator.services.mozilla.com/D24117

--HG--
extra : moz-landing-system : lando
2019-03-21 11:48:33 +00:00

1256 lines
36 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "nsGeolocation.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/CycleCollectedJSContext.h" // for nsAutoMicroTask
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/FeaturePolicyUtils.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/PositionError.h"
#include "mozilla/dom/PositionErrorBinding.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/Telemetry.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/WeakPtr.h"
#include "mozilla/EventStateManager.h"
#include "nsComponentManagerUtils.h"
#include "nsContentPermissionHelper.h"
#include "nsContentUtils.h"
#include "nsGlobalWindow.h"
#include "mozilla/dom/Document.h"
#include "nsINamed.h"
#include "nsIObserverService.h"
#include "nsIScriptError.h"
#include "nsPIDOMWindow.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
class nsIPrincipal;
#ifdef MOZ_WIDGET_ANDROID
# include "AndroidLocationProvider.h"
#endif
#ifdef MOZ_GPSD
# include "GpsdLocationProvider.h"
#endif
#ifdef MOZ_WIDGET_COCOA
# include "CoreLocationLocationProvider.h"
#endif
#ifdef XP_WIN
# include "WindowsLocationProvider.h"
# include "mozilla/WindowsVersion.h"
#endif
// Some limit to the number of get or watch geolocation requests
// that a window can make.
#define MAX_GEO_REQUESTS_PER_WINDOW 1500
// This preference allows to override the "secure context" by
// default policy.
#define PREF_GEO_SECURITY_ALLOWINSECURE "geo.security.allowinsecure"
using mozilla::Unused; // <snicker>
using namespace mozilla;
using namespace mozilla::dom;
class nsGeolocationRequest final
: public ContentPermissionRequestBase,
public nsIGeolocationUpdate,
public SupportsWeakPtr<nsGeolocationRequest> {
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIGEOLOCATIONUPDATE
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsGeolocationRequest,
ContentPermissionRequestBase)
nsGeolocationRequest(Geolocation* aLocator, GeoPositionCallback aCallback,
GeoPositionErrorCallback aErrorCallback,
UniquePtr<PositionOptions>&& aOptions,
uint8_t aProtocolType, nsIEventTarget* aMainThreadTarget,
bool aWatchPositionRequest = false,
int32_t aWatchId = 0);
// nsIContentPermissionRequest
MOZ_CAN_RUN_SCRIPT NS_IMETHOD Cancel(void) override;
MOZ_CAN_RUN_SCRIPT NS_IMETHOD Allow(JS::HandleValue choices) override;
MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsGeolocationRequest)
void Shutdown();
// MOZ_CAN_RUN_SCRIPT_BOUNDARY is OK here because we're always called from a
// runnable. Ideally nsIRunnable::Run and its overloads would just be
// MOZ_CAN_RUN_SCRIPT and then we could be too...
MOZ_CAN_RUN_SCRIPT_BOUNDARY
void SendLocation(nsIDOMGeoPosition* aLocation);
bool WantsHighAccuracy() {
return !mShutdown && mOptions && mOptions->mEnableHighAccuracy;
}
void SetTimeoutTimer();
void StopTimeoutTimer();
MOZ_CAN_RUN_SCRIPT
void NotifyErrorAndShutdown(uint16_t);
using ContentPermissionRequestBase::GetPrincipal;
nsIPrincipal* GetPrincipal();
bool IsWatch() { return mIsWatchPositionRequest; }
int32_t WatchId() { return mWatchId; }
private:
virtual ~nsGeolocationRequest();
class TimerCallbackHolder final : public nsITimerCallback, public nsINamed {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSITIMERCALLBACK
explicit TimerCallbackHolder(nsGeolocationRequest* aRequest)
: mRequest(aRequest) {}
NS_IMETHOD GetName(nsACString& aName) override {
aName.AssignLiteral("nsGeolocationRequest::TimerCallbackHolder");
return NS_OK;
}
private:
~TimerCallbackHolder() = default;
WeakPtr<nsGeolocationRequest> mRequest;
};
// Only called from a timer, so MOZ_CAN_RUN_SCRIPT_BOUNDARY ok for now.
MOZ_CAN_RUN_SCRIPT_BOUNDARY void Notify();
bool mIsWatchPositionRequest;
nsCOMPtr<nsITimer> mTimeoutTimer;
GeoPositionCallback mCallback;
GeoPositionErrorCallback mErrorCallback;
UniquePtr<PositionOptions> mOptions;
RefPtr<Geolocation> mLocator;
int32_t mWatchId;
bool mShutdown;
nsCOMPtr<nsIContentPermissionRequester> mRequester;
uint8_t mProtocolType;
nsCOMPtr<nsIEventTarget> mMainThreadTarget;
};
static UniquePtr<PositionOptions> CreatePositionOptionsCopy(
const PositionOptions& aOptions) {
UniquePtr<PositionOptions> geoOptions = MakeUnique<PositionOptions>();
geoOptions->mEnableHighAccuracy = aOptions.mEnableHighAccuracy;
geoOptions->mMaximumAge = aOptions.mMaximumAge;
geoOptions->mTimeout = aOptions.mTimeout;
return geoOptions;
}
class RequestSendLocationEvent : public Runnable {
public:
RequestSendLocationEvent(nsIDOMGeoPosition* aPosition,
nsGeolocationRequest* aRequest)
: mozilla::Runnable("RequestSendLocationEvent"),
mPosition(aPosition),
mRequest(aRequest) {}
NS_IMETHOD Run() override {
mRequest->SendLocation(mPosition);
return NS_OK;
}
private:
nsCOMPtr<nsIDOMGeoPosition> mPosition;
RefPtr<nsGeolocationRequest> mRequest;
RefPtr<Geolocation> mLocator;
};
////////////////////////////////////////////////////
// nsGeolocationRequest
////////////////////////////////////////////////////
static nsPIDOMWindowInner* ConvertWeakReferenceToWindow(
nsIWeakReference* aWeakPtr) {
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(aWeakPtr);
// This isn't usually safe, but here we're just extracting a raw pointer in
// order to pass it to a base class constructor which will in turn convert it
// into a strong pointer for us.
nsPIDOMWindowInner* raw = window.get();
return raw;
}
nsGeolocationRequest::nsGeolocationRequest(
Geolocation* aLocator, GeoPositionCallback aCallback,
GeoPositionErrorCallback aErrorCallback,
UniquePtr<PositionOptions>&& aOptions, uint8_t aProtocolType,
nsIEventTarget* aMainThreadTarget, bool aWatchPositionRequest,
int32_t aWatchId)
: ContentPermissionRequestBase(
aLocator->GetPrincipal(),
ConvertWeakReferenceToWindow(aLocator->GetOwner()),
NS_LITERAL_CSTRING("geo"), NS_LITERAL_CSTRING("geolocation")),
mIsWatchPositionRequest(aWatchPositionRequest),
mCallback(std::move(aCallback)),
mErrorCallback(std::move(aErrorCallback)),
mOptions(std::move(aOptions)),
mLocator(aLocator),
mWatchId(aWatchId),
mShutdown(false),
mProtocolType(aProtocolType),
mMainThreadTarget(aMainThreadTarget) {
if (nsCOMPtr<nsPIDOMWindowInner> win =
do_QueryReferent(mLocator->GetOwner())) {
}
}
nsGeolocationRequest::~nsGeolocationRequest() { StopTimeoutTimer(); }
NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(nsGeolocationRequest,
ContentPermissionRequestBase,
nsIGeolocationUpdate)
NS_IMPL_ADDREF_INHERITED(nsGeolocationRequest, ContentPermissionRequestBase)
NS_IMPL_RELEASE_INHERITED(nsGeolocationRequest, ContentPermissionRequestBase)
NS_IMPL_CYCLE_COLLECTION_INHERITED(nsGeolocationRequest,
ContentPermissionRequestBase, mCallback,
mErrorCallback, mLocator)
void nsGeolocationRequest::Notify() {
SetTimeoutTimer();
NotifyErrorAndShutdown(PositionError_Binding::TIMEOUT);
}
void nsGeolocationRequest::NotifyErrorAndShutdown(uint16_t aErrorCode) {
MOZ_ASSERT(!mShutdown, "timeout after shutdown");
if (!mIsWatchPositionRequest) {
Shutdown();
mLocator->RemoveRequest(this);
}
NotifyError(aErrorCode);
}
NS_IMETHODIMP
nsGeolocationRequest::Cancel() {
if (mRequester) {
// Record the number of denied requests for regular web content.
// This method is only called when the user explicitly denies the request,
// and is not called when the page is simply unloaded, or similar.
Telemetry::Accumulate(Telemetry::GEOLOCATION_REQUEST_GRANTED,
mProtocolType);
}
if (mLocator->ClearPendingRequest(this)) {
return NS_OK;
}
NotifyError(PositionError_Binding::PERMISSION_DENIED);
return NS_OK;
}
NS_IMETHODIMP
nsGeolocationRequest::Allow(JS::HandleValue aChoices) {
MOZ_ASSERT(aChoices.isUndefined());
if (mRequester) {
// Record the number of granted requests for regular web content.
Telemetry::Accumulate(Telemetry::GEOLOCATION_REQUEST_GRANTED,
mProtocolType + 10);
// Record whether a location callback is fulfilled while the owner window
// is not visible.
bool isVisible = false;
nsCOMPtr<nsPIDOMWindowInner> window = mLocator->GetParentObject();
if (window) {
nsCOMPtr<Document> doc = window->GetDoc();
isVisible = doc && !doc->Hidden();
}
if (IsWatch()) {
mozilla::Telemetry::Accumulate(
mozilla::Telemetry::GEOLOCATION_WATCHPOSITION_VISIBLE, isVisible);
} else {
mozilla::Telemetry::Accumulate(
mozilla::Telemetry::GEOLOCATION_GETCURRENTPOSITION_VISIBLE,
isVisible);
}
}
if (mLocator->ClearPendingRequest(this)) {
return NS_OK;
}
RefPtr<nsGeolocationService> gs =
nsGeolocationService::GetGeolocationService();
bool canUseCache = false;
CachedPositionAndAccuracy lastPosition = gs->GetCachedPosition();
if (lastPosition.position) {
DOMTimeStamp cachedPositionTime_ms;
lastPosition.position->GetTimestamp(&cachedPositionTime_ms);
// check to see if we can use a cached value
// if the user has specified a maximumAge, return a cached value.
if (mOptions && mOptions->mMaximumAge > 0) {
uint32_t maximumAge_ms = mOptions->mMaximumAge;
bool isCachedWithinRequestedAccuracy =
WantsHighAccuracy() <= lastPosition.isHighAccuracy;
bool isCachedWithinRequestedTime =
DOMTimeStamp(PR_Now() / PR_USEC_PER_MSEC - maximumAge_ms) <=
cachedPositionTime_ms;
canUseCache =
isCachedWithinRequestedAccuracy && isCachedWithinRequestedTime;
}
}
gs->UpdateAccuracy(WantsHighAccuracy());
if (canUseCache) {
// okay, we can return a cached position
// getCurrentPosition requests serviced by the cache
// will now be owned by the RequestSendLocationEvent
Update(lastPosition.position);
// After Update is called, getCurrentPosition finishes it's job.
if (!mIsWatchPositionRequest) {
return NS_OK;
}
} else {
// if it is not a watch request and timeout is 0,
// invoke the errorCallback (if present) with TIMEOUT code
if (mOptions && mOptions->mTimeout == 0 && !mIsWatchPositionRequest) {
NotifyError(PositionError_Binding::TIMEOUT);
return NS_OK;
}
}
// Kick off the geo device, if it isn't already running
nsCOMPtr<nsIPrincipal> principal = GetPrincipal();
nsresult rv = gs->StartDevice(principal);
if (NS_FAILED(rv)) {
// Location provider error
NotifyError(PositionError_Binding::POSITION_UNAVAILABLE);
return NS_OK;
}
if (mIsWatchPositionRequest || !canUseCache) {
// let the locator know we're pending
// we will now be owned by the locator
mLocator->NotifyAllowedRequest(this);
}
SetTimeoutTimer();
return NS_OK;
}
void nsGeolocationRequest::SetTimeoutTimer() {
MOZ_ASSERT(!mShutdown, "set timeout after shutdown");
StopTimeoutTimer();
if (mOptions && mOptions->mTimeout != 0 && mOptions->mTimeout != 0x7fffffff) {
RefPtr<TimerCallbackHolder> holder = new TimerCallbackHolder(this);
NS_NewTimerWithCallback(getter_AddRefs(mTimeoutTimer), holder,
mOptions->mTimeout, nsITimer::TYPE_ONE_SHOT);
}
}
void nsGeolocationRequest::StopTimeoutTimer() {
if (mTimeoutTimer) {
mTimeoutTimer->Cancel();
mTimeoutTimer = nullptr;
}
}
void nsGeolocationRequest::SendLocation(nsIDOMGeoPosition* aPosition) {
if (mShutdown) {
// Ignore SendLocationEvents issued before we were cleared.
return;
}
if (mOptions && mOptions->mMaximumAge > 0) {
DOMTimeStamp positionTime_ms;
aPosition->GetTimestamp(&positionTime_ms);
const uint32_t maximumAge_ms = mOptions->mMaximumAge;
const bool isTooOld = DOMTimeStamp(PR_Now() / PR_USEC_PER_MSEC -
maximumAge_ms) > positionTime_ms;
if (isTooOld) {
return;
}
}
RefPtr<mozilla::dom::Position> wrapped;
if (aPosition) {
nsCOMPtr<nsIDOMGeoPositionCoords> coords;
aPosition->GetCoords(getter_AddRefs(coords));
if (coords) {
wrapped = new mozilla::dom::Position(ToSupports(mLocator), aPosition);
}
}
if (!wrapped) {
NotifyError(PositionError_Binding::POSITION_UNAVAILABLE);
return;
}
if (!mIsWatchPositionRequest) {
// Cancel timer and position updates in case the position
// callback spins the event loop
Shutdown();
}
nsAutoMicroTask mt;
if (mCallback.HasWebIDLCallback()) {
RefPtr<PositionCallback> callback = mCallback.GetWebIDLCallback();
MOZ_ASSERT(callback);
callback->Call(*wrapped);
} else {
nsIDOMGeoPositionCallback* callback = mCallback.GetXPCOMCallback();
MOZ_ASSERT(callback);
callback->HandleEvent(aPosition);
}
if (mIsWatchPositionRequest && !mShutdown) {
SetTimeoutTimer();
}
MOZ_ASSERT(mShutdown || mIsWatchPositionRequest,
"non-shutdown getCurrentPosition request after callback!");
}
nsIPrincipal* nsGeolocationRequest::GetPrincipal() {
if (!mLocator) {
return nullptr;
}
return mLocator->GetPrincipal();
}
NS_IMETHODIMP
nsGeolocationRequest::Update(nsIDOMGeoPosition* aPosition) {
nsCOMPtr<nsIRunnable> ev = new RequestSendLocationEvent(aPosition, this);
mMainThreadTarget->Dispatch(ev.forget());
return NS_OK;
}
NS_IMETHODIMP
nsGeolocationRequest::NotifyError(uint16_t aErrorCode) {
MOZ_ASSERT(NS_IsMainThread());
RefPtr<PositionError> positionError = new PositionError(mLocator, aErrorCode);
positionError->NotifyCallback(mErrorCallback);
return NS_OK;
}
void nsGeolocationRequest::Shutdown() {
MOZ_ASSERT(!mShutdown, "request shutdown twice");
mShutdown = true;
StopTimeoutTimer();
// If there are no other high accuracy requests, the geolocation service will
// notify the provider to switch to the default accuracy.
if (mOptions && mOptions->mEnableHighAccuracy) {
RefPtr<nsGeolocationService> gs =
nsGeolocationService::GetGeolocationService();
if (gs) {
gs->UpdateAccuracy();
}
}
}
////////////////////////////////////////////////////
// nsGeolocationRequest::TimerCallbackHolder
////////////////////////////////////////////////////
NS_IMPL_ISUPPORTS(nsGeolocationRequest::TimerCallbackHolder, nsITimerCallback,
nsINamed)
NS_IMETHODIMP
nsGeolocationRequest::TimerCallbackHolder::Notify(nsITimer*) {
if (mRequest && mRequest->mLocator) {
RefPtr<nsGeolocationRequest> request(mRequest);
request->Notify();
}
return NS_OK;
}
////////////////////////////////////////////////////
// nsGeolocationService
////////////////////////////////////////////////////
NS_INTERFACE_MAP_BEGIN(nsGeolocationService)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIGeolocationUpdate)
NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(nsGeolocationService)
NS_IMPL_RELEASE(nsGeolocationService)
static bool sGeoEnabled = true;
static int32_t sProviderTimeout = 6000; // Time, in milliseconds, to wait for
// the location provider to spin up.
nsresult nsGeolocationService::Init() {
Preferences::AddIntVarCache(&sProviderTimeout, "geo.timeout",
sProviderTimeout);
Preferences::AddBoolVarCache(&sGeoEnabled, "geo.enabled", sGeoEnabled);
if (!sGeoEnabled) {
return NS_ERROR_FAILURE;
}
if (XRE_IsContentProcess()) {
return NS_OK;
}
// geolocation service can be enabled -> now register observer
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (!obs) {
return NS_ERROR_FAILURE;
}
obs->AddObserver(this, "xpcom-shutdown", false);
#ifdef MOZ_WIDGET_ANDROID
mProvider = new AndroidLocationProvider();
#endif
#ifdef MOZ_WIDGET_GTK
# ifdef MOZ_GPSD
if (Preferences::GetBool("geo.provider.use_gpsd", false)) {
mProvider = new GpsdLocationProvider();
}
# endif
#endif
#ifdef MOZ_WIDGET_COCOA
if (Preferences::GetBool("geo.provider.use_corelocation", true)) {
mProvider = new CoreLocationLocationProvider();
}
#endif
#ifdef XP_WIN
if (Preferences::GetBool("geo.provider.ms-windows-location", false) &&
IsWin8OrLater()) {
mProvider = new WindowsLocationProvider();
}
#endif
if (Preferences::GetBool("geo.provider.use_mls", false)) {
mProvider = do_CreateInstance("@mozilla.org/geolocation/mls-provider;1");
}
// Override platform-specific providers with the default (network)
// provider while testing. Our tests are currently not meant to exercise
// the provider, and some tests rely on the network provider being used.
// "geo.provider.testing" is always set for all plain and browser chrome
// mochitests, and also for xpcshell tests.
if (!mProvider || Preferences::GetBool("geo.provider.testing", false)) {
nsCOMPtr<nsIGeolocationProvider> geoTestProvider =
do_GetService(NS_GEOLOCATION_PROVIDER_CONTRACTID);
if (geoTestProvider) {
mProvider = geoTestProvider;
}
}
return NS_OK;
}
nsGeolocationService::~nsGeolocationService() = default;
NS_IMETHODIMP
nsGeolocationService::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
if (!strcmp("xpcom-shutdown", aTopic)) {
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (obs) {
obs->RemoveObserver(this, "xpcom-shutdown");
}
for (uint32_t i = 0; i < mGeolocators.Length(); i++) {
mGeolocators[i]->Shutdown();
}
StopDevice();
return NS_OK;
}
if (!strcmp("timer-callback", aTopic)) {
// decide if we can close down the service.
for (uint32_t i = 0; i < mGeolocators.Length(); i++)
if (mGeolocators[i]->HasActiveCallbacks()) {
SetDisconnectTimer();
return NS_OK;
}
// okay to close up.
StopDevice();
Update(nullptr);
return NS_OK;
}
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsGeolocationService::Update(nsIDOMGeoPosition* aSomewhere) {
if (aSomewhere) {
SetCachedPosition(aSomewhere);
}
for (uint32_t i = 0; i < mGeolocators.Length(); i++) {
mGeolocators[i]->Update(aSomewhere);
}
return NS_OK;
}
NS_IMETHODIMP
nsGeolocationService::NotifyError(uint16_t aErrorCode) {
// nsTArray doesn't have a constructors that takes a different-type TArray.
nsTArray<RefPtr<Geolocation>> geolocators;
geolocators.AppendElements(mGeolocators);
for (uint32_t i = 0; i < geolocators.Length(); i++) {
// MOZ_KnownLive because the stack array above keeps it alive.
MOZ_KnownLive(geolocators[i])->NotifyError(aErrorCode);
}
return NS_OK;
}
void nsGeolocationService::SetCachedPosition(nsIDOMGeoPosition* aPosition) {
mLastPosition.position = aPosition;
mLastPosition.isHighAccuracy = mHigherAccuracy;
}
CachedPositionAndAccuracy nsGeolocationService::GetCachedPosition() {
return mLastPosition;
}
nsresult nsGeolocationService::StartDevice(nsIPrincipal* aPrincipal) {
if (!sGeoEnabled) {
return NS_ERROR_NOT_AVAILABLE;
}
// We do not want to keep the geolocation devices online
// indefinitely.
// Close them down after a reasonable period of inactivivity.
SetDisconnectTimer();
if (XRE_IsContentProcess()) {
ContentChild* cpc = ContentChild::GetSingleton();
cpc->SendAddGeolocationListener(IPC::Principal(aPrincipal),
HighAccuracyRequested());
return NS_OK;
}
// Start them up!
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (!obs) {
return NS_ERROR_FAILURE;
}
if (!mProvider) {
return NS_ERROR_FAILURE;
}
nsresult rv;
if (NS_FAILED(rv = mProvider->Startup()) ||
NS_FAILED(rv = mProvider->Watch(this))) {
NotifyError(PositionError_Binding::POSITION_UNAVAILABLE);
return rv;
}
obs->NotifyObservers(mProvider, "geolocation-device-events", u"starting");
return NS_OK;
}
void nsGeolocationService::SetDisconnectTimer() {
if (!mDisconnectTimer) {
mDisconnectTimer = NS_NewTimer();
} else {
mDisconnectTimer->Cancel();
}
mDisconnectTimer->Init(this, sProviderTimeout, 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::UpdateAccuracy(bool aForceHigh) {
bool highRequired = aForceHigh || HighAccuracyRequested();
if (XRE_IsContentProcess()) {
ContentChild* cpc = ContentChild::GetSingleton();
if (cpc->IsAlive()) {
cpc->SendSetGeolocationHigherAccuracy(highRequired);
}
return;
}
mProvider->SetHighAccuracy(!mHigherAccuracy && highRequired);
mHigherAccuracy = highRequired;
}
void nsGeolocationService::StopDevice() {
if (mDisconnectTimer) {
mDisconnectTimer->Cancel();
mDisconnectTimer = nullptr;
}
if (XRE_IsContentProcess()) {
ContentChild* cpc = ContentChild::GetSingleton();
cpc->SendRemoveGeolocationListener();
return; // bail early
}
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (!obs) {
return;
}
if (!mProvider) {
return;
}
mHigherAccuracy = false;
mProvider->Shutdown();
obs->NotifyObservers(mProvider, "geolocation-device-events", u"shutdown");
}
StaticRefPtr<nsGeolocationService> nsGeolocationService::sService;
already_AddRefed<nsGeolocationService>
nsGeolocationService::GetGeolocationService() {
RefPtr<nsGeolocationService> result;
if (nsGeolocationService::sService) {
result = nsGeolocationService::sService;
return result.forget();
}
result = new nsGeolocationService();
if (NS_FAILED(result->Init())) {
return nullptr;
}
ClearOnShutdown(&nsGeolocationService::sService);
nsGeolocationService::sService = result;
return result.forget();
}
void nsGeolocationService::AddLocator(Geolocation* aLocator) {
mGeolocators.AppendElement(aLocator);
}
void nsGeolocationService::RemoveLocator(Geolocation* aLocator) {
mGeolocators.RemoveElement(aLocator);
}
////////////////////////////////////////////////////
// Geolocation
////////////////////////////////////////////////////
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Geolocation)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(Geolocation)
NS_IMPL_CYCLE_COLLECTING_RELEASE(Geolocation)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Geolocation, mPendingCallbacks,
mWatchingCallbacks, mPendingRequests)
Geolocation::Geolocation()
: mProtocolType(ProtocolType::OTHER), mLastWatchId(0) {}
Geolocation::~Geolocation() {
if (mService) {
Shutdown();
}
}
StaticRefPtr<Geolocation> Geolocation::sNonWindowSingleton;
already_AddRefed<Geolocation> Geolocation::NonWindowSingleton() {
if (sNonWindowSingleton) {
return do_AddRef(sNonWindowSingleton);
}
RefPtr<Geolocation> result = new Geolocation();
DebugOnly<nsresult> rv = result->Init();
MOZ_ASSERT(NS_SUCCEEDED(rv), "How can this fail?");
ClearOnShutdown(&sNonWindowSingleton);
sNonWindowSingleton = result;
return result.forget();
}
nsresult Geolocation::Init(nsPIDOMWindowInner* aContentDom) {
// Remember the window
if (aContentDom) {
mOwner = do_GetWeakReference(aContentDom);
if (!mOwner) {
return NS_ERROR_FAILURE;
}
// Grab the principal of the document
nsCOMPtr<Document> doc = aContentDom->GetDoc();
if (!doc) {
return NS_ERROR_FAILURE;
}
mPrincipal = doc->NodePrincipal();
nsCOMPtr<nsIURI> uri;
nsresult rv = mPrincipal->GetURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
if (uri) {
bool isHttp;
rv = uri->SchemeIs("http", &isHttp);
NS_ENSURE_SUCCESS(rv, rv);
bool isHttps;
rv = uri->SchemeIs("https", &isHttps);
NS_ENSURE_SUCCESS(rv, rv);
// Store the protocol to send via telemetry later.
if (isHttp) {
mProtocolType = ProtocolType::HTTP;
} else if (isHttps) {
mProtocolType = ProtocolType::HTTPS;
}
}
}
// If no aContentDom was passed into us, we are being used
// by chrome/c++ and have no mOwner, no mPrincipal, and no need
// to prompt.
mService = nsGeolocationService::GetGeolocationService();
if (mService) {
mService->AddLocator(this);
}
return NS_OK;
}
void Geolocation::Shutdown() {
// Release all callbacks
mPendingCallbacks.Clear();
mWatchingCallbacks.Clear();
if (mService) {
mService->RemoveLocator(this);
mService->UpdateAccuracy();
}
mService = nullptr;
mPrincipal = nullptr;
}
nsPIDOMWindowInner* Geolocation::GetParentObject() const {
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mOwner);
return window.get();
}
bool Geolocation::HasActiveCallbacks() {
return mPendingCallbacks.Length() || mWatchingCallbacks.Length();
}
bool Geolocation::HighAccuracyRequested() {
for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) {
if (mWatchingCallbacks[i]->WantsHighAccuracy()) {
return true;
}
}
for (uint32_t i = 0; i < mPendingCallbacks.Length(); i++) {
if (mPendingCallbacks[i]->WantsHighAccuracy()) {
return true;
}
}
return false;
}
void Geolocation::RemoveRequest(nsGeolocationRequest* aRequest) {
bool requestWasKnown = (mPendingCallbacks.RemoveElement(aRequest) !=
mWatchingCallbacks.RemoveElement(aRequest));
Unused << requestWasKnown;
}
NS_IMETHODIMP
Geolocation::Update(nsIDOMGeoPosition* aSomewhere) {
if (!WindowOwnerStillExists()) {
Shutdown();
return NS_OK;
}
if (aSomewhere) {
nsCOMPtr<nsIDOMGeoPositionCoords> coords;
aSomewhere->GetCoords(getter_AddRefs(coords));
if (coords) {
double accuracy = -1;
coords->GetAccuracy(&accuracy);
mozilla::Telemetry::Accumulate(
mozilla::Telemetry::GEOLOCATION_ACCURACY_EXPONENTIAL, accuracy);
}
}
for (uint32_t i = mPendingCallbacks.Length(); i > 0; i--) {
mPendingCallbacks[i - 1]->Update(aSomewhere);
RemoveRequest(mPendingCallbacks[i - 1]);
}
// notify everyone that is watching
for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) {
mWatchingCallbacks[i]->Update(aSomewhere);
}
return NS_OK;
}
NS_IMETHODIMP
Geolocation::NotifyError(uint16_t aErrorCode) {
if (!WindowOwnerStillExists()) {
Shutdown();
return NS_OK;
}
mozilla::Telemetry::Accumulate(mozilla::Telemetry::GEOLOCATION_ERROR, true);
for (uint32_t i = mPendingCallbacks.Length(); i > 0; i--) {
RefPtr<nsGeolocationRequest> request = mPendingCallbacks[i - 1];
request->NotifyErrorAndShutdown(aErrorCode);
// NotifyErrorAndShutdown() removes the request from the array
}
// notify everyone that is watching
for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) {
RefPtr<nsGeolocationRequest> request = mWatchingCallbacks[i];
request->NotifyErrorAndShutdown(aErrorCode);
}
return NS_OK;
}
bool Geolocation::IsAlreadyCleared(nsGeolocationRequest* aRequest) {
for (uint32_t i = 0, length = mClearedWatchIDs.Length(); i < length; ++i) {
if (mClearedWatchIDs[i] == aRequest->WatchId()) {
return true;
}
}
return false;
}
bool Geolocation::ShouldBlockInsecureRequests() const {
if (Preferences::GetBool(PREF_GEO_SECURITY_ALLOWINSECURE, false)) {
return false;
}
nsCOMPtr<nsPIDOMWindowInner> win = do_QueryReferent(mOwner);
if (!win) {
return false;
}
nsCOMPtr<Document> doc = win->GetDoc();
if (!doc) {
return false;
}
if (!nsGlobalWindowInner::Cast(win)->IsSecureContext()) {
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
NS_LITERAL_CSTRING("DOM"), doc,
nsContentUtils::eDOM_PROPERTIES,
"GeolocationInsecureRequestIsForbidden");
return true;
}
return false;
}
bool Geolocation::FeaturePolicyBlocked() const {
nsCOMPtr<nsPIDOMWindowInner> win = do_QueryReferent(mOwner);
if (!win) {
return true;
}
nsCOMPtr<Document> doc = win->GetExtantDoc();
if (!doc) {
return false;
}
return FeaturePolicyUtils::IsFeatureAllowed(doc,
NS_LITERAL_STRING("geolocation"));
}
bool Geolocation::ClearPendingRequest(nsGeolocationRequest* aRequest) {
if (aRequest->IsWatch() && this->IsAlreadyCleared(aRequest)) {
this->NotifyAllowedRequest(aRequest);
this->ClearWatch(aRequest->WatchId());
return true;
}
return false;
}
void Geolocation::GetCurrentPosition(PositionCallback& aCallback,
PositionErrorCallback* aErrorCallback,
const PositionOptions& aOptions,
CallerType aCallerType, ErrorResult& aRv) {
nsresult rv = GetCurrentPosition(
GeoPositionCallback(&aCallback), GeoPositionErrorCallback(aErrorCallback),
CreatePositionOptionsCopy(aOptions), aCallerType);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
}
}
static nsIEventTarget* MainThreadTarget(Geolocation* geo) {
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(geo->GetOwner());
if (!window) {
return GetMainThreadEventTarget();
}
return nsGlobalWindowInner::Cast(window)->EventTargetFor(
mozilla::TaskCategory::Other);
}
nsresult Geolocation::GetCurrentPosition(GeoPositionCallback callback,
GeoPositionErrorCallback errorCallback,
UniquePtr<PositionOptions>&& options,
CallerType aCallerType) {
if (mPendingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) {
return NS_ERROR_NOT_AVAILABLE;
}
// After this we hand over ownership of options to our nsGeolocationRequest.
// Count the number of requests per protocol/scheme.
Telemetry::Accumulate(Telemetry::GEOLOCATION_GETCURRENTPOSITION_SECURE_ORIGIN,
static_cast<uint8_t>(mProtocolType));
nsIEventTarget* target = MainThreadTarget(this);
RefPtr<nsGeolocationRequest> request = new nsGeolocationRequest(
this, std::move(callback), std::move(errorCallback), std::move(options),
static_cast<uint8_t>(mProtocolType), target);
if (!sGeoEnabled || ShouldBlockInsecureRequests() ||
!FeaturePolicyBlocked()) {
request->RequestDelayedTask(target,
nsGeolocationRequest::DelayedTaskType::Deny);
return NS_OK;
}
if (!mOwner && aCallerType != CallerType::System) {
return NS_ERROR_FAILURE;
}
if (mOwner) {
if (!RegisterRequestWithPrompt(request)) {
return NS_ERROR_NOT_AVAILABLE;
}
return NS_OK;
}
if (aCallerType != CallerType::System) {
return NS_ERROR_FAILURE;
}
request->RequestDelayedTask(target,
nsGeolocationRequest::DelayedTaskType::Allow);
return NS_OK;
}
int32_t Geolocation::WatchPosition(PositionCallback& aCallback,
PositionErrorCallback* aErrorCallback,
const PositionOptions& aOptions,
CallerType aCallerType, ErrorResult& aRv) {
return WatchPosition(GeoPositionCallback(&aCallback),
GeoPositionErrorCallback(aErrorCallback),
CreatePositionOptionsCopy(aOptions), aCallerType, aRv);
}
int32_t Geolocation::WatchPosition(
nsIDOMGeoPositionCallback* aCallback,
nsIDOMGeoPositionErrorCallback* aErrorCallback,
UniquePtr<PositionOptions>&& aOptions) {
MOZ_ASSERT(aCallback);
return WatchPosition(GeoPositionCallback(aCallback),
GeoPositionErrorCallback(aErrorCallback),
std::move(aOptions), CallerType::System, IgnoreErrors());
}
// On errors we return -1 because that's not a valid watch id and will
// get ignored in clearWatch.
int32_t Geolocation::WatchPosition(GeoPositionCallback aCallback,
GeoPositionErrorCallback aErrorCallback,
UniquePtr<PositionOptions>&& aOptions,
CallerType aCallerType, ErrorResult& aRv) {
if (mWatchingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return -1;
}
// Count the number of requests per protocol/scheme.
Telemetry::Accumulate(Telemetry::GEOLOCATION_WATCHPOSITION_SECURE_ORIGIN,
static_cast<uint8_t>(mProtocolType));
// The watch ID:
int32_t watchId = mLastWatchId++;
nsIEventTarget* target = MainThreadTarget(this);
RefPtr<nsGeolocationRequest> request = new nsGeolocationRequest(
this, std::move(aCallback), std::move(aErrorCallback),
std::move(aOptions), static_cast<uint8_t>(mProtocolType), target, true,
watchId);
if (!sGeoEnabled || ShouldBlockInsecureRequests() ||
!FeaturePolicyBlocked()) {
request->RequestDelayedTask(target,
nsGeolocationRequest::DelayedTaskType::Deny);
return watchId;
}
if (!mOwner && aCallerType != CallerType::System) {
aRv.Throw(NS_ERROR_FAILURE);
return -1;
}
if (mOwner) {
if (!RegisterRequestWithPrompt(request)) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return -1;
}
return watchId;
}
if (aCallerType != CallerType::System) {
aRv.Throw(NS_ERROR_FAILURE);
return -1;
}
request->Allow(JS::UndefinedHandleValue);
return watchId;
}
void Geolocation::ClearWatch(int32_t aWatchId) {
if (aWatchId < 0) {
return;
}
if (!mClearedWatchIDs.Contains(aWatchId)) {
mClearedWatchIDs.AppendElement(aWatchId);
}
for (uint32_t i = 0, length = mWatchingCallbacks.Length(); i < length; ++i) {
if (mWatchingCallbacks[i]->WatchId() == aWatchId) {
mWatchingCallbacks[i]->Shutdown();
RemoveRequest(mWatchingCallbacks[i]);
mClearedWatchIDs.RemoveElement(aWatchId);
break;
}
}
// make sure we also search through the pending requests lists for
// watches to clear...
for (uint32_t i = 0, length = mPendingRequests.Length(); i < length; ++i) {
if (mPendingRequests[i]->IsWatch() &&
(mPendingRequests[i]->WatchId() == aWatchId)) {
mPendingRequests[i]->Shutdown();
mPendingRequests.RemoveElementAt(i);
break;
}
}
}
bool Geolocation::WindowOwnerStillExists() {
// an owner was never set when Geolocation
// was created, which means that this object
// is being used without a window.
if (mOwner == nullptr) {
return true;
}
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mOwner);
if (window) {
nsPIDOMWindowOuter* outer = window->GetOuterWindow();
if (!outer || outer->GetCurrentInnerWindow() != window || outer->Closed()) {
return false;
}
}
return true;
}
void Geolocation::NotifyAllowedRequest(nsGeolocationRequest* aRequest) {
if (aRequest->IsWatch()) {
mWatchingCallbacks.AppendElement(aRequest);
} else {
mPendingCallbacks.AppendElement(aRequest);
}
}
bool Geolocation::RegisterRequestWithPrompt(nsGeolocationRequest* request) {
nsIEventTarget* target = MainThreadTarget(this);
ContentPermissionRequestBase::PromptResult pr = request->CheckPromptPrefs();
if (pr == ContentPermissionRequestBase::PromptResult::Granted) {
request->RequestDelayedTask(target,
nsGeolocationRequest::DelayedTaskType::Allow);
return true;
}
if (pr == ContentPermissionRequestBase::PromptResult::Denied) {
request->RequestDelayedTask(target,
nsGeolocationRequest::DelayedTaskType::Deny);
return true;
}
request->RequestDelayedTask(target,
nsGeolocationRequest::DelayedTaskType::Request);
return true;
}
JSObject* Geolocation::WrapObject(JSContext* aCtx,
JS::Handle<JSObject*> aGivenProto) {
return Geolocation_Binding::Wrap(aCtx, this, aGivenProto);
}