/* -*- 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/. */
class nsGeolocationRequest final : public ContentPermissionRequestBase, public nsIGeolocationUpdate, public SupportsWeakPtr { public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIGEOLOCATIONUPDATE
// 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();
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;
}
/** * When the promise for the cancel dialog is resolved or rejected, we should * stop waiting for permission. If it was granted then the * SystemGeolocationPermissionRequest should already be resolved, so we do * nothing. Otherwise, we were either cancelled or got an error, so we cancel * the SystemGeolocationPermissionRequest.
*/ class CancelSystemGeolocationPermissionRequest : public PromiseNativeHandler { public:
NS_DECL_ISUPPORTS
/* static */ void Geolocation::ReallowWithSystemPermissionOrCancel(
BrowsingContext* aBrowsingContext,
geolocation::ParentRequestResolver&& aResolver) { // Make sure we don't return without responding to the geolocation request. auto denyPermissionOnError =
MakeScopeExit([&aResolver]() MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA {
aResolver(GeolocationPermissionStatus::Error);
});
NS_ENSURE_TRUE_VOID(aBrowsingContext);
nsCOMPtr<nsIStringBundle> bundle;
nsCOMPtr<nsIStringBundleService> sbs =
do_GetService(NS_STRINGBUNDLE_CONTRACTID);
NS_ENSURE_TRUE_VOID(sbs);
auto cancelRequestOnError = MakeScopeExit([&]() { // Stop waiting for the system permission and just leave it up to the user.
permissionRequest->Stop();
});
// The dialog should include a cancel button if Gecko is prompting the user // for system permission. It should have no buttons if the OS will be // doing the prompting. bool geckoWillPrompt =
GetLocationOSPermission() ==
geolocation::SystemGeolocationPermissionBehavior::GeckoWillPromptUser; // This combination of flags removes all buttons and adds a spinner to the // title. constauto kSpinnerNoButtonFlags =
nsIPromptService::BUTTON_NONE | nsIPromptService::SHOW_SPINNER; // This combination of flags indicates there is only one button labeled // "Cancel". constauto kCancelButtonFlags =
nsIPromptService::BUTTON_TITLE_CANCEL * nsIPromptService::BUTTON_POS_0;
RefPtr<mozilla::dom::Promise> tabBlockingDialogPromise;
rv = promptSvc->AsyncConfirmEx(
aBrowsingContext, nsIPromptService::MODAL_TYPE_TAB, title.get(),
message.get(),
geckoWillPrompt ? kCancelButtonFlags : kSpinnerNoButtonFlags, nullptr,
nullptr, nullptr, nullptr, false, JS::UndefinedHandleValue,
getter_AddRefs(tabBlockingDialogPromise));
NS_ENSURE_SUCCESS_VOID(rv);
MOZ_ASSERT(tabBlockingDialogPromise);
// If the tab blocking dialog promise is resolved or rejected then the dialog // is no longer visible so we should stop waiting for permission, whether it // was granted or not.
tabBlockingDialogPromise->AppendNativeHandler( new CancelSystemGeolocationPermissionRequest(permissionRequest));
if (mLocator->ClearPendingRequest(this)) { return NS_OK;
}
if (mBehavior != SystemGeolocationPermissionBehavior::NoPrompt) { // Asynchronously present the system dialog or open system preferences // (RequestGeolocationPermissionFromUser will know which to do), and wait // for the permission to change or the request to be canceled. If the // permission is (maybe) granted then it will call Allow again. It actually // will also re-call Allow if the permission is denied, in order to get the // "denied permission" behavior.
mBehavior = SystemGeolocationPermissionBehavior::NoPrompt;
RefPtr<BrowsingContext> browsingContext = mWindow->GetBrowsingContext(); if (ContentChild* cc = ContentChild::GetSingleton()) {
cc->SendRequestGeolocationPermissionFromUser(
browsingContext,
[self = RefPtr{this}](GeolocationPermissionStatus aResult)
MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA {
self->Allow(JS::UndefinedHandleValue);
},
[self = RefPtr{this}](mozilla::ipc::ResponseRejectReason aReason)
MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA {
self->Allow(JS::UndefinedHandleValue);
}); return NS_OK;
}
bool canUseCache = false;
CachedPositionAndAccuracy lastPosition = gs->GetCachedPosition(); if (lastPosition.position) {
EpochTimeStamp 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 =
EpochTimeStamp(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 its 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(GeolocationPositionError_Binding::TIMEOUT); return NS_OK;
}
}
// Non-cached location request bool allowedRequest = mIsWatchPositionRequest || !canUseCache; if (allowedRequest) { // let the locator know we're pending // we will now be owned by the locator
mLocator->NotifyAllowedRequest(this);
}
// Kick off the geo device, if it isn't already running
nsresult rv = gs->StartDevice();
if (NS_FAILED(rv)) { if (allowedRequest) {
mLocator->RemoveRequest(this);
} // Location provider error
NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE); return NS_OK;
}
SetTimeoutTimer();
return NS_OK;
}
void nsGeolocationRequest::SetTimeoutTimer() {
MOZ_ASSERT(!mShutdown, "set timeout after shutdown");
void nsGeolocationRequest::SendLocation(nsIDOMGeoPosition* aPosition) { if (mShutdown) { // Ignore SendLocationEvents issued before we were cleared. return;
}
// 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();
}
}
}
nsresult nsGeolocationService::Init() { if (!StaticPrefs::geo_enabled()) { 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
if (!mProvider && StaticPrefs::geo_provider_use_geoclue()) {
nsCOMPtr<nsIGeolocationProvider> gcProvider = new GeoclueLocationProvider();
MOZ_LOG(gGeolocationLog, LogLevel::Debug,
("Checking GeoclueLocationProvider")); // The Startup() method will only succeed if Geoclue is available on D-Bus if (NS_SUCCEEDED(gcProvider->Startup())) {
gcProvider->Shutdown();
mProvider = std::move(gcProvider);
MOZ_LOG(gGeolocationLog, LogLevel::Debug,
("Selected GeoclueLocationProvider"));
glean::geolocation::linux_provider
.EnumGet(glean::geolocation::LinuxProviderLabel::eGeoclue)
.Set(true);
}
} # 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)) {
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;
}
}
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) {
MOZ_LOG(
gGeolocationLog, LogLevel::Debug,
("nsGeolocationService::NotifyError with error code: %u", 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;
}
nsresult nsGeolocationService::StartDevice() { if (!StaticPrefs::geo_enabled()) { 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();
bool nsGeolocationService::HighAccuracyRequested() { for (uint32_t i = 0; i < mGeolocators.Length(); i++) { if (mGeolocators[i]->HighAccuracyRequested()) { returntrue;
}
}
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(); // Store the protocol to send via telemetry later. if (mPrincipal->SchemeIs("http")) {
mProtocolType = ProtocolType::HTTP;
} elseif (mPrincipal->SchemeIs("https")) {
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();
}
bool Geolocation::HighAccuracyRequested() { for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) { if (mWatchingCallbacks[i]->WantsHighAccuracy()) { returntrue;
}
}
for (uint32_t i = 0; i < mPendingCallbacks.Length(); i++) { if (mPendingCallbacks[i]->WantsHighAccuracy()) { returntrue;
}
}
// Don't update position if window is not fully active or the document is // hidden. We keep the pending callaback and watchers waiting for the next // update.
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(this->GetOwner()); if (window) {
nsCOMPtr<Document> document = window->GetDoc(); bool isHidden = document && document->Hidden(); if (isHidden || !window->IsFullyActive()) { return NS_OK;
}
}
if (aSomewhere) {
nsCOMPtr<nsIDOMGeoPositionCoords> coords;
aSomewhere->GetCoords(getter_AddRefs(coords)); if (coords) { double accuracy = -1;
coords->GetAccuracy(&accuracy);
glean::geolocation::accuracy.AccumulateSingleSample( static_cast<uint64_t>(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) {
MOZ_LOG(gGeolocationLog, LogLevel::Debug,
("Geolocation::NotifyError with error code: %u", aErrorCode)); if (!WindowOwnerStillExists()) {
Shutdown(); return NS_OK;
}
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::IsFullyActiveOrChrome() { // For regular content window, only allow this proceed if the window is "fully // active". if (nsPIDOMWindowInner* window = this->GetParentObject()) { return window->IsFullyActive();
} // Calls coming from chrome code don't have window, so we can proceed. returntrue;
}
bool Geolocation::IsAlreadyCleared(nsGeolocationRequest* aRequest) { for (uint32_t i = 0, length = mClearedWatchIDs.Length(); i < length; ++i) { if (mClearedWatchIDs[i] == aRequest->WatchId()) { returntrue;
}
}
returnfalse;
}
bool Geolocation::ShouldBlockInsecureRequests() const { if (Preferences::GetBool(PREF_GEO_SECURITY_ALLOWINSECURE, false)) { returnfalse;
}
nsCOMPtr<nsPIDOMWindowInner> win = do_QueryReferent(mOwner); if (!win) { returnfalse;
}
nsCOMPtr<Document> doc = win->GetDoc(); if (!doc) { returnfalse;
}
if (!nsGlobalWindowInner::Cast(win)->IsSecureContext()) {
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns, doc,
nsContentUtils::eDOM_PROPERTIES, "GeolocationInsecureRequestIsForbidden"); returntrue;
}
// On errors we return 0 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 (!IsFullyActiveOrChrome()) {
RefPtr<GeolocationPositionError> positionError = new GeolocationPositionError( this, GeolocationPositionError_Binding::POSITION_UNAVAILABLE);
positionError->NotifyCallback(aErrorCallback); return 0;
}
if (mWatchingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE); return 0;
}
// The watch ID:
int32_t watchId = mLastWatchId++;
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) { returntrue;
}
void Geolocation::RequestIfPermitted(nsGeolocationRequest* request) { auto getPermission = [request = RefPtr{request}](auto aPermission) { switch (aPermission) { case geolocation::SystemGeolocationPermissionBehavior::
SystemWillPromptUser: case geolocation::SystemGeolocationPermissionBehavior::
GeckoWillPromptUser:
request->SetPromptBehavior(aPermission); break; case geolocation::SystemGeolocationPermissionBehavior::NoPrompt: // Either location access is already permitted by OS or the system // permission UX is not available for this platform. Do nothing. break; default:
MOZ_ASSERT_UNREACHABLE( "unexpected GeolocationPermissionBehavior value"); break;
}
RegisterRequestWithPrompt(request);
};
if (auto* contentChild = ContentChild::GetSingleton()) {
contentChild->SendGetSystemGeolocationPermissionBehavior(
std::move(getPermission),
[request =
RefPtr{request}](mozilla::ipc::ResponseRejectReason aReason) {
NS_WARNING("Error sending GetSystemGeolocationPermissionBehavior"); // We still need to run the location request, even if we don't // have permission.
RegisterRequestWithPrompt(request);
});
} else {
MOZ_ASSERT(XRE_IsParentProcess());
getPermission(GetLocationOSPermission());
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.