Geolocation.cpp (48785B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "Geolocation.h" 8 9 #include "GeolocationIPCUtils.h" 10 #include "GeolocationSystem.h" 11 #include "mozilla/ClearOnShutdown.h" 12 #include "mozilla/CycleCollectedJSContext.h" // for nsAutoMicroTask 13 #include "mozilla/EventStateManager.h" 14 #include "mozilla/Preferences.h" 15 #include "mozilla/Services.h" 16 #include "mozilla/StaticPrefs_geo.h" 17 #include "mozilla/StaticPtr.h" 18 #include "mozilla/UniquePtr.h" 19 #include "mozilla/WeakPtr.h" 20 #include "mozilla/dom/BrowserChild.h" 21 #include "mozilla/dom/ContentChild.h" 22 #include "mozilla/dom/Document.h" 23 #include "mozilla/dom/GeolocationPositionError.h" 24 #include "mozilla/dom/GeolocationPositionErrorBinding.h" 25 #include "mozilla/dom/PermissionMessageUtils.h" 26 #include "mozilla/glean/DomGeolocationMetrics.h" 27 #include "mozilla/ipc/MessageChannel.h" 28 #include "nsComponentManagerUtils.h" 29 #include "nsContentPermissionHelper.h" 30 #include "nsContentUtils.h" 31 #include "nsGlobalWindowInner.h" 32 #include "nsINamed.h" 33 #include "nsIObserverService.h" 34 #include "nsIPromptService.h" 35 #include "nsIScriptError.h" 36 #include "nsPIDOMWindow.h" 37 #include "nsServiceManagerUtils.h" 38 #include "nsThreadUtils.h" 39 #include "nsXULAppAPI.h" 40 41 class nsIPrincipal; 42 43 #ifdef MOZ_WIDGET_ANDROID 44 # include "AndroidLocationProvider.h" 45 #endif 46 47 #ifdef MOZ_ENABLE_DBUS 48 # include "GeoclueLocationProvider.h" 49 # include "PortalLocationProvider.h" 50 # include "mozilla/WidgetUtilsGtk.h" 51 #endif 52 53 #ifdef MOZ_WIDGET_COCOA 54 # include "CoreLocationLocationProvider.h" 55 #endif 56 57 #ifdef XP_WIN 58 # include "WindowsLocationProvider.h" 59 #endif 60 61 // Some limit to the number of get or watch geolocation requests 62 // that a window can make. 63 #define MAX_GEO_REQUESTS_PER_WINDOW 1500 64 65 // This preference allows to override the "secure context" by 66 // default policy. 67 #define PREF_GEO_SECURITY_ALLOWINSECURE "geo.security.allowinsecure" 68 69 using namespace mozilla; 70 using namespace mozilla::dom; 71 using namespace mozilla::dom::geolocation; 72 73 mozilla::LazyLogModule gGeolocationLog("Geolocation"); 74 75 class nsGeolocationRequest final : public ContentPermissionRequestBase, 76 public nsIGeolocationUpdate, 77 public SupportsWeakPtr { 78 public: 79 NS_DECL_ISUPPORTS_INHERITED 80 NS_DECL_NSIGEOLOCATIONUPDATE 81 82 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsGeolocationRequest, 83 ContentPermissionRequestBase) 84 85 nsGeolocationRequest(Geolocation* aLocator, GeoPositionCallback aCallback, 86 GeoPositionErrorCallback aErrorCallback, 87 UniquePtr<PositionOptions>&& aOptions, 88 nsIEventTarget* aMainThreadSerialEventTarget, 89 bool aWatchPositionRequest = false, 90 int32_t aWatchId = 0); 91 92 // nsIContentPermissionRequest 93 MOZ_CAN_RUN_SCRIPT NS_IMETHOD Cancel(void) override; 94 MOZ_CAN_RUN_SCRIPT NS_IMETHOD Allow(JS::Handle<JS::Value> choices) override; 95 NS_IMETHOD GetTypes(nsIArray** aTypes) override; 96 97 void Shutdown(); 98 99 // MOZ_CAN_RUN_SCRIPT_BOUNDARY is OK here because we're always called from a 100 // runnable. Ideally nsIRunnable::Run and its overloads would just be 101 // MOZ_CAN_RUN_SCRIPT and then we could be too... 102 MOZ_CAN_RUN_SCRIPT_BOUNDARY 103 void SendLocation(nsIDOMGeoPosition* aLocation); 104 bool WantsHighAccuracy() { 105 return !mShutdown && mOptions && mOptions->mEnableHighAccuracy; 106 } 107 void SetTimeoutTimer(); 108 void StopTimeoutTimer(); 109 MOZ_CAN_RUN_SCRIPT 110 void NotifyErrorAndShutdown(uint16_t); 111 using ContentPermissionRequestBase::GetPrincipal; 112 nsIPrincipal* GetPrincipal(); 113 114 bool IsWatch() { return mIsWatchPositionRequest; } 115 int32_t WatchId() { return mWatchId; } 116 117 void SetPromptBehavior( 118 geolocation::SystemGeolocationPermissionBehavior aBehavior) { 119 mBehavior = aBehavior; 120 } 121 122 private: 123 virtual ~nsGeolocationRequest(); 124 125 class TimerCallbackHolder final : public nsITimerCallback, public nsINamed { 126 public: 127 NS_DECL_ISUPPORTS 128 NS_DECL_NSITIMERCALLBACK 129 130 explicit TimerCallbackHolder(nsGeolocationRequest* aRequest) 131 : mRequest(aRequest) {} 132 133 NS_IMETHOD GetName(nsACString& aName) override { 134 aName.AssignLiteral("nsGeolocationRequest::TimerCallbackHolder"); 135 return NS_OK; 136 } 137 138 private: 139 ~TimerCallbackHolder() = default; 140 WeakPtr<nsGeolocationRequest> mRequest; 141 }; 142 143 // Only called from a timer, so MOZ_CAN_RUN_SCRIPT_BOUNDARY ok for now. 144 MOZ_CAN_RUN_SCRIPT_BOUNDARY void Notify(); 145 146 bool mIsWatchPositionRequest; 147 148 nsCOMPtr<nsITimer> mTimeoutTimer; 149 GeoPositionCallback mCallback; 150 GeoPositionErrorCallback mErrorCallback; 151 UniquePtr<PositionOptions> mOptions; 152 153 RefPtr<Geolocation> mLocator; 154 155 int32_t mWatchId; 156 bool mShutdown; 157 bool mSeenAnySignal = false; 158 nsCOMPtr<nsIEventTarget> mMainThreadSerialEventTarget; 159 160 SystemGeolocationPermissionBehavior mBehavior; 161 }; 162 163 static UniquePtr<PositionOptions> CreatePositionOptionsCopy( 164 const PositionOptions& aOptions) { 165 UniquePtr<PositionOptions> geoOptions = MakeUnique<PositionOptions>(); 166 167 geoOptions->mEnableHighAccuracy = aOptions.mEnableHighAccuracy; 168 geoOptions->mMaximumAge = aOptions.mMaximumAge; 169 geoOptions->mTimeout = aOptions.mTimeout; 170 171 return geoOptions; 172 } 173 174 class RequestSendLocationEvent : public Runnable { 175 public: 176 RequestSendLocationEvent(nsIDOMGeoPosition* aPosition, 177 nsGeolocationRequest* aRequest) 178 : mozilla::Runnable("RequestSendLocationEvent"), 179 mPosition(aPosition), 180 mRequest(aRequest) {} 181 182 NS_IMETHOD Run() override { 183 mRequest->SendLocation(mPosition); 184 return NS_OK; 185 } 186 187 private: 188 nsCOMPtr<nsIDOMGeoPosition> mPosition; 189 RefPtr<nsGeolocationRequest> mRequest; 190 RefPtr<Geolocation> mLocator; 191 }; 192 193 //////////////////////////////////////////////////// 194 // nsGeolocationRequest 195 //////////////////////////////////////////////////// 196 197 static nsPIDOMWindowInner* ConvertWeakReferenceToWindow( 198 nsIWeakReference* aWeakPtr) { 199 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(aWeakPtr); 200 // This isn't usually safe, but here we're just extracting a raw pointer in 201 // order to pass it to a base class constructor which will in turn convert it 202 // into a strong pointer for us. 203 nsPIDOMWindowInner* raw = window.get(); 204 return raw; 205 } 206 207 nsGeolocationRequest::nsGeolocationRequest( 208 Geolocation* aLocator, GeoPositionCallback aCallback, 209 GeoPositionErrorCallback aErrorCallback, 210 UniquePtr<PositionOptions>&& aOptions, 211 nsIEventTarget* aMainThreadSerialEventTarget, bool aWatchPositionRequest, 212 int32_t aWatchId) 213 : ContentPermissionRequestBase( 214 aLocator->GetPrincipal(), 215 ConvertWeakReferenceToWindow(aLocator->GetOwner()), "geo"_ns, 216 "geolocation"_ns), 217 mIsWatchPositionRequest(aWatchPositionRequest), 218 mCallback(std::move(aCallback)), 219 mErrorCallback(std::move(aErrorCallback)), 220 mOptions(std::move(aOptions)), 221 mLocator(aLocator), 222 mWatchId(aWatchId), 223 mShutdown(false), 224 mMainThreadSerialEventTarget(aMainThreadSerialEventTarget), 225 mBehavior(SystemGeolocationPermissionBehavior::NoPrompt) {} 226 227 nsGeolocationRequest::~nsGeolocationRequest() { StopTimeoutTimer(); } 228 229 NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(nsGeolocationRequest, 230 ContentPermissionRequestBase, 231 nsIGeolocationUpdate) 232 233 NS_IMPL_ADDREF_INHERITED(nsGeolocationRequest, ContentPermissionRequestBase) 234 NS_IMPL_RELEASE_INHERITED(nsGeolocationRequest, ContentPermissionRequestBase) 235 NS_IMPL_CYCLE_COLLECTION_WEAK_PTR_INHERITED(nsGeolocationRequest, 236 ContentPermissionRequestBase, 237 mCallback, mErrorCallback, mLocator) 238 239 void nsGeolocationRequest::Notify() { 240 MOZ_LOG(gGeolocationLog, LogLevel::Debug, ("nsGeolocationRequest::Notify")); 241 NotifyErrorAndShutdown(GeolocationPositionError_Binding::TIMEOUT); 242 } 243 244 void nsGeolocationRequest::NotifyErrorAndShutdown(uint16_t aErrorCode) { 245 MOZ_LOG(gGeolocationLog, LogLevel::Debug, 246 ("nsGeolocationRequest::NotifyErrorAndShutdown with error code: %u", 247 aErrorCode)); 248 MOZ_ASSERT(!mShutdown, "timeout after shutdown"); 249 if (!mIsWatchPositionRequest) { 250 Shutdown(); 251 mLocator->RemoveRequest(this); 252 } 253 254 NotifyError(aErrorCode); 255 } 256 257 NS_IMETHODIMP 258 nsGeolocationRequest::Cancel() { 259 if (mLocator->ClearPendingRequest(this)) { 260 return NS_OK; 261 } 262 263 NotifyError(GeolocationPositionError_Binding::PERMISSION_DENIED); 264 return NS_OK; 265 } 266 267 /** 268 * When the promise for the cancel dialog is resolved or rejected, we should 269 * stop waiting for permission. If it was granted then the 270 * SystemGeolocationPermissionRequest should already be resolved, so we do 271 * nothing. Otherwise, we were either cancelled or got an error, so we cancel 272 * the SystemGeolocationPermissionRequest. 273 */ 274 class CancelSystemGeolocationPermissionRequest : public PromiseNativeHandler { 275 public: 276 NS_DECL_ISUPPORTS 277 278 explicit CancelSystemGeolocationPermissionRequest( 279 SystemGeolocationPermissionRequest* aRequest) 280 : mRequest(aRequest) {} 281 282 MOZ_CAN_RUN_SCRIPT_BOUNDARY 283 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, 284 ErrorResult& aRv) override { 285 mRequest->Stop(); 286 } 287 288 MOZ_CAN_RUN_SCRIPT_BOUNDARY 289 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, 290 ErrorResult& aRv) override { 291 mRequest->Stop(); 292 } 293 294 private: 295 ~CancelSystemGeolocationPermissionRequest() = default; 296 RefPtr<SystemGeolocationPermissionRequest> mRequest; 297 }; 298 299 NS_IMPL_ISUPPORTS0(CancelSystemGeolocationPermissionRequest) 300 301 /* static */ 302 void Geolocation::ReallowWithSystemPermissionOrCancel( 303 BrowsingContext* aBrowsingContext, 304 geolocation::ParentRequestResolver&& aResolver) { 305 // Make sure we don't return without responding to the geolocation request. 306 auto denyPermissionOnError = 307 MakeScopeExit([&aResolver]() MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { 308 aResolver(GeolocationPermissionStatus::Error); 309 }); 310 311 NS_ENSURE_TRUE_VOID(aBrowsingContext); 312 313 nsCOMPtr<nsIStringBundle> bundle; 314 nsCOMPtr<nsIStringBundleService> sbs = 315 do_GetService(NS_STRINGBUNDLE_CONTRACTID); 316 NS_ENSURE_TRUE_VOID(sbs); 317 318 sbs->CreateBundle("chrome://browser/locale/browser.properties", 319 getter_AddRefs(bundle)); 320 NS_ENSURE_TRUE_VOID(bundle); 321 322 nsAutoString title; 323 nsresult rv = 324 bundle->GetStringFromName("geolocation.systemSettingsTitle", title); 325 NS_ENSURE_SUCCESS_VOID(rv); 326 327 nsAutoString brandName; 328 rv = nsContentUtils::GetLocalizedString(nsContentUtils::eBRAND_PROPERTIES, 329 "brandShortName", brandName); 330 NS_ENSURE_SUCCESS_VOID(rv); 331 AutoTArray<nsString, 1> formatParams; 332 formatParams.AppendElement(brandName); 333 nsAutoString message; 334 rv = bundle->FormatStringFromName("geolocation.systemSettingsMessage", 335 formatParams, message); 336 NS_ENSURE_SUCCESS_VOID(rv); 337 338 // We MUST do this because aResolver is moved below. 339 denyPermissionOnError.release(); 340 341 RefPtr<SystemGeolocationPermissionRequest> permissionRequest = 342 geolocation::RequestLocationPermissionFromUser(aBrowsingContext, 343 std::move(aResolver)); 344 NS_ENSURE_TRUE_VOID(permissionRequest); 345 346 auto cancelRequestOnError = MakeScopeExit([&]() { 347 // Stop waiting for the system permission and just leave it up to the user. 348 permissionRequest->Stop(); 349 }); 350 351 nsCOMPtr<nsIPromptService> promptSvc = 352 do_GetService("@mozilla.org/prompter;1", &rv); 353 NS_ENSURE_SUCCESS_VOID(rv); 354 355 // The dialog should include a cancel button if Gecko is prompting the user 356 // for system permission. It should have no buttons if the OS will be 357 // doing the prompting. 358 bool geckoWillPrompt = 359 GetLocationOSPermission() == 360 geolocation::SystemGeolocationPermissionBehavior::GeckoWillPromptUser; 361 // This combination of flags removes all buttons and adds a spinner to the 362 // title. 363 const auto kSpinnerNoButtonFlags = 364 nsIPromptService::BUTTON_NONE | nsIPromptService::SHOW_SPINNER; 365 // This combination of flags indicates there is only one button labeled 366 // "Cancel". 367 const auto kCancelButtonFlags = 368 nsIPromptService::BUTTON_TITLE_CANCEL * nsIPromptService::BUTTON_POS_0; 369 RefPtr<mozilla::dom::Promise> tabBlockingDialogPromise; 370 rv = promptSvc->AsyncConfirmEx( 371 aBrowsingContext, nsIPromptService::MODAL_TYPE_TAB, title.get(), 372 message.get(), 373 geckoWillPrompt ? kCancelButtonFlags : kSpinnerNoButtonFlags, nullptr, 374 nullptr, nullptr, nullptr, false, JS::UndefinedHandleValue, 375 getter_AddRefs(tabBlockingDialogPromise)); 376 NS_ENSURE_SUCCESS_VOID(rv); 377 MOZ_ASSERT(tabBlockingDialogPromise); 378 379 // If the tab blocking dialog promise is resolved or rejected then the dialog 380 // is no longer visible so we should stop waiting for permission, whether it 381 // was granted or not. 382 tabBlockingDialogPromise->AppendNativeHandler( 383 new CancelSystemGeolocationPermissionRequest(permissionRequest)); 384 385 cancelRequestOnError.release(); 386 } 387 388 NS_IMETHODIMP 389 nsGeolocationRequest::Allow(JS::Handle<JS::Value> aChoices) { 390 MOZ_ASSERT(aChoices.isUndefined()); 391 392 if (mLocator->ClearPendingRequest(this)) { 393 return NS_OK; 394 } 395 396 if (mBehavior != SystemGeolocationPermissionBehavior::NoPrompt) { 397 // Asynchronously present the system dialog or open system preferences 398 // (RequestGeolocationPermissionFromUser will know which to do), and wait 399 // for the permission to change or the request to be canceled. If the 400 // permission is (maybe) granted then it will call Allow again. It actually 401 // will also re-call Allow if the permission is denied, in order to get the 402 // "denied permission" behavior. 403 mBehavior = SystemGeolocationPermissionBehavior::NoPrompt; 404 RefPtr<BrowsingContext> browsingContext = mWindow->GetBrowsingContext(); 405 if (ContentChild* cc = ContentChild::GetSingleton()) { 406 cc->SendRequestGeolocationPermissionFromUser( 407 browsingContext, 408 [self = RefPtr{this}](GeolocationPermissionStatus aResult) 409 MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { 410 self->Allow(JS::UndefinedHandleValue); 411 }, 412 [self = RefPtr{this}](mozilla::ipc::ResponseRejectReason aReason) 413 MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { 414 self->Allow(JS::UndefinedHandleValue); 415 }); 416 return NS_OK; 417 } 418 419 Geolocation::ReallowWithSystemPermissionOrCancel( 420 browsingContext, 421 [self = RefPtr{this}](GeolocationPermissionStatus aResult) 422 MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { 423 self->Allow(JS::UndefinedHandleValue); 424 }); 425 return NS_OK; 426 } 427 428 RefPtr<nsGeolocationService> gs = nsGeolocationService::GetGeolocationService( 429 mLocator->GetBrowsingContext()); 430 bool canUseCache = false; 431 CachedPositionAndAccuracy lastPosition = gs->GetCachedPosition(); 432 if (lastPosition.position) { 433 EpochTimeStamp cachedPositionTime_ms; 434 lastPosition.position->GetTimestamp(&cachedPositionTime_ms); 435 // check to see if we can use a cached value 436 // if the user has specified a maximumAge, return a cached value. 437 if (mOptions && mOptions->mMaximumAge > 0) { 438 uint32_t maximumAge_ms = mOptions->mMaximumAge; 439 bool isCachedWithinRequestedAccuracy = 440 WantsHighAccuracy() <= lastPosition.isHighAccuracy; 441 bool isCachedWithinRequestedTime = 442 EpochTimeStamp(PR_Now() / PR_USEC_PER_MSEC - maximumAge_ms) <= 443 cachedPositionTime_ms; 444 canUseCache = 445 isCachedWithinRequestedAccuracy && isCachedWithinRequestedTime; 446 } 447 } 448 449 // Enforce using cache in case the geolocation override server is set, 450 // since this server can return only cached values. 451 if (!canUseCache && gs != nsGeolocationService::sService.get()) { 452 canUseCache = true; 453 } 454 455 if (XRE_IsParentProcess()) { 456 // On content process this info will be passed together via 457 // SendAddGeolocationListener called by StartDevice below 458 gs->UpdateAccuracy(WantsHighAccuracy()); 459 } 460 461 if (canUseCache) { 462 // okay, we can return a cached position 463 // getCurrentPosition requests serviced by the cache 464 // will now be owned by the RequestSendLocationEvent 465 Update(lastPosition.position); 466 467 // After Update is called, getCurrentPosition finishes its job. 468 if (!mIsWatchPositionRequest) { 469 return NS_OK; 470 } 471 472 } else { 473 // if it is not a watch request and timeout is 0, 474 // invoke the errorCallback (if present) with TIMEOUT code 475 if (mOptions && mOptions->mTimeout == 0 && !mIsWatchPositionRequest) { 476 NotifyError(GeolocationPositionError_Binding::TIMEOUT); 477 return NS_OK; 478 } 479 } 480 481 // Non-cached location request 482 bool allowedRequest = mIsWatchPositionRequest || !canUseCache; 483 if (allowedRequest) { 484 // let the locator know we're pending 485 // we will now be owned by the locator 486 mLocator->NotifyAllowedRequest(this); 487 } 488 489 // Kick off the geo device, if it isn't already running 490 nsresult rv = gs->StartDevice(); 491 492 if (NS_FAILED(rv)) { 493 if (allowedRequest) { 494 mLocator->RemoveRequest(this); 495 } 496 // Location provider error 497 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE); 498 return NS_OK; 499 } 500 501 SetTimeoutTimer(); 502 503 return NS_OK; 504 } 505 506 void nsGeolocationRequest::SetTimeoutTimer() { 507 MOZ_ASSERT(!mShutdown, "set timeout after shutdown"); 508 509 StopTimeoutTimer(); 510 511 if (mOptions && mOptions->mTimeout != 0 && mOptions->mTimeout != 0x7fffffff) { 512 RefPtr<TimerCallbackHolder> holder = new TimerCallbackHolder(this); 513 NS_NewTimerWithCallback(getter_AddRefs(mTimeoutTimer), holder, 514 mOptions->mTimeout, nsITimer::TYPE_ONE_SHOT); 515 } 516 } 517 518 void nsGeolocationRequest::StopTimeoutTimer() { 519 if (mTimeoutTimer) { 520 mTimeoutTimer->Cancel(); 521 mTimeoutTimer = nullptr; 522 } 523 } 524 525 void nsGeolocationRequest::SendLocation(nsIDOMGeoPosition* aPosition) { 526 if (mShutdown) { 527 // Ignore SendLocationEvents issued before we were cleared. 528 return; 529 } 530 531 if (mOptions && mOptions->mMaximumAge > 0) { 532 EpochTimeStamp positionTime_ms; 533 aPosition->GetTimestamp(&positionTime_ms); 534 const uint32_t maximumAge_ms = mOptions->mMaximumAge; 535 const bool isTooOld = EpochTimeStamp(PR_Now() / PR_USEC_PER_MSEC - 536 maximumAge_ms) > positionTime_ms; 537 if (isTooOld) { 538 return; 539 } 540 } 541 542 RefPtr<mozilla::dom::GeolocationPosition> wrapped; 543 544 if (aPosition) { 545 nsCOMPtr<nsIDOMGeoPositionCoords> coords; 546 aPosition->GetCoords(getter_AddRefs(coords)); 547 if (coords) { 548 wrapped = new mozilla::dom::GeolocationPosition(ToSupports(mLocator), 549 aPosition); 550 } 551 } 552 553 if (!wrapped) { 554 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE); 555 return; 556 } 557 558 if (mIsWatchPositionRequest) { 559 // The initial signal for watch request is observed, after this we do not 560 // set a timer as the geolocation device is free to not send extra position 561 // if the device is simply not moving. 562 // 563 // See also https://w3c.github.io/geolocation/#dfn-request-a-position 564 // Step 7.5.1: "Wait for a significant change of geographic position. What 565 // constitutes a significant change of geographic position is left to the 566 // implementation." 567 StopTimeoutTimer(); 568 } else { 569 // Cancel timer and position updates in case the position 570 // callback spins the event loop 571 Shutdown(); 572 } 573 574 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 575 obs->NotifyObservers(wrapped, "geolocation-position-events", 576 u"location-updated"); 577 578 nsAutoMicroTask mt; 579 if (mCallback.HasWebIDLCallback()) { 580 RefPtr<PositionCallback> callback = mCallback.GetWebIDLCallback(); 581 582 MOZ_ASSERT(callback); 583 callback->Call(*wrapped); 584 } else { 585 nsIDOMGeoPositionCallback* callback = mCallback.GetXPCOMCallback(); 586 MOZ_ASSERT(callback); 587 callback->HandleEvent(aPosition); 588 } 589 590 MOZ_ASSERT(mShutdown || mIsWatchPositionRequest, 591 "non-shutdown getCurrentPosition request after callback!"); 592 } 593 594 nsIPrincipal* nsGeolocationRequest::GetPrincipal() { 595 if (!mLocator) { 596 return nullptr; 597 } 598 return mLocator->GetPrincipal(); 599 } 600 601 NS_IMETHODIMP 602 nsGeolocationRequest::Update(nsIDOMGeoPosition* aPosition) { 603 if (!mSeenAnySignal) { 604 mSeenAnySignal = true; 605 glean::geolocation::request_result 606 .EnumGet(glean::geolocation::RequestResultLabel::eSuccess) 607 .Add(); 608 } 609 nsCOMPtr<nsIRunnable> ev = new RequestSendLocationEvent(aPosition, this); 610 mMainThreadSerialEventTarget->Dispatch(ev.forget()); 611 return NS_OK; 612 } 613 614 static glean::geolocation::RequestResultLabel MapErrorToLabel( 615 uint16_t aErrorCode) { 616 using Label = glean::geolocation::RequestResultLabel; 617 switch (aErrorCode) { 618 case GeolocationPositionError_Binding::PERMISSION_DENIED: 619 return Label::ePermissionDenied; 620 case GeolocationPositionError_Binding::POSITION_UNAVAILABLE: 621 return Label::ePositionUnavailable; 622 case GeolocationPositionError_Binding::TIMEOUT: 623 return Label::eTimeout; 624 default: 625 MOZ_CRASH("Unknown geolocation error label"); 626 return Label::ePositionUnavailable; 627 } 628 } 629 630 NS_IMETHODIMP 631 nsGeolocationRequest::NotifyError(uint16_t aErrorCode) { 632 MOZ_LOG( 633 gGeolocationLog, LogLevel::Debug, 634 ("nsGeolocationRequest::NotifyError with error code: %u", aErrorCode)); 635 MOZ_ASSERT(NS_IsMainThread()); 636 if (!mSeenAnySignal) { 637 mSeenAnySignal = true; 638 glean::geolocation::request_result.EnumGet(MapErrorToLabel(aErrorCode)) 639 .Add(); 640 } 641 RefPtr<GeolocationPositionError> positionError = 642 new GeolocationPositionError(mLocator, static_cast<int16_t>(aErrorCode)); 643 positionError->NotifyCallback(mErrorCallback); 644 return NS_OK; 645 } 646 647 void nsGeolocationRequest::Shutdown() { 648 MOZ_ASSERT(!mShutdown, "request shutdown twice"); 649 mShutdown = true; 650 651 StopTimeoutTimer(); 652 653 // If there are no other high accuracy requests, the geolocation service will 654 // notify the provider to switch to the default accuracy. 655 if (mOptions && mOptions->mEnableHighAccuracy) { 656 RefPtr<nsGeolocationService> gs = 657 nsGeolocationService::GetGeolocationService( 658 mLocator->GetBrowsingContext()); 659 if (gs) { 660 gs->UpdateAccuracy(); 661 } 662 } 663 } 664 665 NS_IMETHODIMP 666 nsGeolocationRequest::GetTypes(nsIArray** aTypes) { 667 AutoTArray<nsString, 2> options; 668 669 switch (mBehavior) { 670 case SystemGeolocationPermissionBehavior::SystemWillPromptUser: 671 options.AppendElement(u"sysdlg"_ns); 672 break; 673 case SystemGeolocationPermissionBehavior::GeckoWillPromptUser: 674 options.AppendElement(u"syssetting"_ns); 675 break; 676 default: 677 break; 678 } 679 return nsContentPermissionUtils::CreatePermissionArray(mType, options, 680 aTypes); 681 } 682 683 //////////////////////////////////////////////////// 684 // nsGeolocationRequest::TimerCallbackHolder 685 //////////////////////////////////////////////////// 686 687 NS_IMPL_ISUPPORTS(nsGeolocationRequest::TimerCallbackHolder, nsITimerCallback, 688 nsINamed) 689 690 NS_IMETHODIMP 691 nsGeolocationRequest::TimerCallbackHolder::Notify(nsITimer*) { 692 if (mRequest && mRequest->mLocator) { 693 RefPtr<nsGeolocationRequest> request(mRequest); 694 request->Notify(); 695 } 696 697 return NS_OK; 698 } 699 700 //////////////////////////////////////////////////// 701 // nsGeolocationService 702 //////////////////////////////////////////////////// 703 NS_INTERFACE_MAP_BEGIN(nsGeolocationService) 704 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIGeolocationUpdate) 705 NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate) 706 NS_INTERFACE_MAP_ENTRY(nsIObserver) 707 NS_INTERFACE_MAP_END 708 709 NS_IMPL_ADDREF(nsGeolocationService) 710 NS_IMPL_RELEASE(nsGeolocationService) 711 712 nsresult nsGeolocationService::Init() { 713 if (!StaticPrefs::geo_enabled()) { 714 return NS_ERROR_FAILURE; 715 } 716 717 if (XRE_IsContentProcess()) { 718 return NS_OK; 719 } 720 721 // geolocation service can be enabled -> now register observer 722 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 723 if (!obs) { 724 return NS_ERROR_FAILURE; 725 } 726 727 obs->AddObserver(this, "xpcom-shutdown", false); 728 729 #ifdef MOZ_WIDGET_ANDROID 730 mProvider = new AndroidLocationProvider(); 731 #endif 732 733 #ifdef MOZ_WIDGET_GTK 734 # ifdef MOZ_ENABLE_DBUS 735 if (!mProvider && widget::ShouldUsePortal(widget::PortalKind::Location)) { 736 mProvider = new PortalLocationProvider(); 737 MOZ_LOG(gGeolocationLog, LogLevel::Debug, 738 ("Selected PortalLocationProvider")); 739 glean::geolocation::linux_provider 740 .EnumGet(glean::geolocation::LinuxProviderLabel::ePortal) 741 .Set(true); 742 } 743 744 if (!mProvider && StaticPrefs::geo_provider_use_geoclue()) { 745 nsCOMPtr<nsIGeolocationProvider> gcProvider = new GeoclueLocationProvider(); 746 MOZ_LOG(gGeolocationLog, LogLevel::Debug, 747 ("Checking GeoclueLocationProvider")); 748 // The Startup() method will only succeed if Geoclue is available on D-Bus 749 if (NS_SUCCEEDED(gcProvider->Startup())) { 750 gcProvider->Shutdown(); 751 mProvider = std::move(gcProvider); 752 MOZ_LOG(gGeolocationLog, LogLevel::Debug, 753 ("Selected GeoclueLocationProvider")); 754 glean::geolocation::linux_provider 755 .EnumGet(glean::geolocation::LinuxProviderLabel::eGeoclue) 756 .Set(true); 757 } 758 } 759 # endif 760 #endif 761 762 #ifdef MOZ_WIDGET_COCOA 763 if (Preferences::GetBool("geo.provider.use_corelocation", true)) { 764 mProvider = new CoreLocationLocationProvider(); 765 } 766 #endif 767 768 #ifdef XP_WIN 769 if (Preferences::GetBool("geo.provider.ms-windows-location", false)) { 770 mProvider = new WindowsLocationProvider(); 771 } 772 #endif 773 774 if (Preferences::GetBool("geo.provider.use_mls", false)) { 775 mProvider = do_CreateInstance("@mozilla.org/geolocation/mls-provider;1"); 776 } 777 778 // Override platform-specific providers with the default (network) 779 // provider while testing. Our tests are currently not meant to exercise 780 // the provider, and some tests rely on the network provider being used. 781 // "geo.provider.testing" is always set for all plain and browser chrome 782 // mochitests, and also for xpcshell tests. 783 if (!mProvider || Preferences::GetBool("geo.provider.testing", false)) { 784 nsCOMPtr<nsIGeolocationProvider> geoTestProvider = 785 do_GetService(NS_GEOLOCATION_PROVIDER_CONTRACTID); 786 787 if (geoTestProvider) { 788 mProvider = geoTestProvider; 789 } 790 } 791 792 return NS_OK; 793 } 794 795 nsGeolocationService::~nsGeolocationService() = default; 796 797 NS_IMETHODIMP 798 nsGeolocationService::Observe(nsISupports* aSubject, const char* aTopic, 799 const char16_t* aData) { 800 if (!strcmp("xpcom-shutdown", aTopic)) { 801 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 802 if (obs) { 803 obs->RemoveObserver(this, "xpcom-shutdown"); 804 } 805 806 for (uint32_t i = 0; i < mGeolocators.Length(); i++) { 807 mGeolocators[i]->Shutdown(); 808 } 809 StopDevice(); 810 811 return NS_OK; 812 } 813 814 if (!strcmp("timer-callback", aTopic)) { 815 // decide if we can close down the service. 816 for (uint32_t i = 0; i < mGeolocators.Length(); i++) 817 if (mGeolocators[i]->HasActiveCallbacks()) { 818 SetDisconnectTimer(); 819 return NS_OK; 820 } 821 822 // okay to close up. 823 StopDevice(); 824 Update(nullptr); 825 return NS_OK; 826 } 827 828 return NS_ERROR_FAILURE; 829 } 830 831 NS_IMETHODIMP 832 nsGeolocationService::Update(nsIDOMGeoPosition* aSomewhere) { 833 if (aSomewhere) { 834 mStarting.reset(); 835 SetCachedPosition(aSomewhere); 836 } 837 838 for (uint32_t i = 0; i < mGeolocators.Length(); i++) { 839 mGeolocators[i]->Update(aSomewhere); 840 } 841 842 return NS_OK; 843 } 844 845 NS_IMETHODIMP 846 nsGeolocationService::NotifyError(uint16_t aErrorCode) { 847 MOZ_LOG( 848 gGeolocationLog, LogLevel::Debug, 849 ("nsGeolocationService::NotifyError with error code: %u", aErrorCode)); 850 // nsTArray doesn't have a constructors that takes a different-type TArray. 851 nsTArray<RefPtr<Geolocation>> geolocators; 852 geolocators.AppendElements(mGeolocators); 853 for (uint32_t i = 0; i < geolocators.Length(); i++) { 854 // MOZ_KnownLive because the stack array above keeps it alive. 855 MOZ_KnownLive(geolocators[i])->NotifyError(aErrorCode); 856 } 857 return NS_OK; 858 } 859 860 void nsGeolocationService::SetCachedPosition(nsIDOMGeoPosition* aPosition) { 861 mLastPosition.position = aPosition; 862 mLastPosition.isHighAccuracy = mHigherAccuracy; 863 } 864 865 CachedPositionAndAccuracy nsGeolocationService::GetCachedPosition() { 866 return mLastPosition; 867 } 868 869 nsresult nsGeolocationService::StartDevice() { 870 if (!StaticPrefs::geo_enabled()) { 871 return NS_ERROR_NOT_AVAILABLE; 872 } 873 874 // We do not want to keep the geolocation devices online 875 // indefinitely. 876 // Close them down after a reasonable period of inactivity. 877 SetDisconnectTimer(); 878 879 if (XRE_IsContentProcess()) { 880 bool highAccuracyRequested = HighAccuracyRequested(); 881 if (mStarting.isSome() && *mStarting == highAccuracyRequested) { 882 // Already being started 883 return NS_OK; 884 } 885 mStarting = Some(highAccuracyRequested); 886 ContentChild* cpc = ContentChild::GetSingleton(); 887 if (!cpc->SendAddGeolocationListener(highAccuracyRequested)) { 888 return NS_ERROR_NOT_AVAILABLE; 889 } 890 return NS_OK; 891 } 892 893 // Start them up! 894 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 895 if (!obs) { 896 return NS_ERROR_FAILURE; 897 } 898 899 if (!mProvider) { 900 return NS_ERROR_FAILURE; 901 } 902 903 nsresult rv; 904 905 if (NS_FAILED(rv = mProvider->Startup()) || 906 NS_FAILED(rv = mProvider->Watch(this))) { 907 NotifyError(GeolocationPositionError_Binding::POSITION_UNAVAILABLE); 908 return rv; 909 } 910 911 obs->NotifyObservers(mProvider, "geolocation-device-events", u"starting"); 912 913 return NS_OK; 914 } 915 916 void nsGeolocationService::SetDisconnectTimer() { 917 if (!mDisconnectTimer) { 918 mDisconnectTimer = NS_NewTimer(); 919 } else { 920 mDisconnectTimer->Cancel(); 921 } 922 923 mDisconnectTimer->Init(this, StaticPrefs::geo_timeout(), 924 nsITimer::TYPE_ONE_SHOT); 925 } 926 927 bool nsGeolocationService::HighAccuracyRequested() { 928 for (uint32_t i = 0; i < mGeolocators.Length(); i++) { 929 if (mGeolocators[i]->HighAccuracyRequested()) { 930 return true; 931 } 932 } 933 934 return false; 935 } 936 937 void nsGeolocationService::UpdateAccuracy(bool aForceHigh) { 938 bool highRequired = aForceHigh || HighAccuracyRequested(); 939 940 if (XRE_IsContentProcess()) { 941 ContentChild* cpc = ContentChild::GetSingleton(); 942 if (cpc->IsAlive()) { 943 cpc->SendSetGeolocationHigherAccuracy(highRequired); 944 } 945 946 return; 947 } 948 949 mProvider->SetHighAccuracy(!mHigherAccuracy && highRequired); 950 mHigherAccuracy = highRequired; 951 } 952 953 void nsGeolocationService::StopDevice() { 954 if (mDisconnectTimer) { 955 mDisconnectTimer->Cancel(); 956 mDisconnectTimer = nullptr; 957 } 958 959 if (XRE_IsContentProcess()) { 960 mStarting.reset(); 961 ContentChild* cpc = ContentChild::GetSingleton(); 962 cpc->SendRemoveGeolocationListener(); 963 964 return; // bail early 965 } 966 967 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 968 if (!obs) { 969 return; 970 } 971 972 if (!mProvider) { 973 return; 974 } 975 976 mHigherAccuracy = false; 977 978 mProvider->Shutdown(); 979 obs->NotifyObservers(mProvider, "geolocation-device-events", u"shutdown"); 980 } 981 982 StaticRefPtr<nsGeolocationService> nsGeolocationService::sService; 983 984 already_AddRefed<nsGeolocationService> 985 nsGeolocationService::GetGeolocationService( 986 mozilla::dom::BrowsingContext* aBrowsingContext) { 987 RefPtr<nsGeolocationService> result; 988 if (aBrowsingContext) { 989 result = aBrowsingContext->GetGeolocationServiceOverride(); 990 991 if (result) { 992 return result.forget(); 993 } 994 } 995 if (nsGeolocationService::sService) { 996 result = nsGeolocationService::sService; 997 998 return result.forget(); 999 } 1000 1001 result = new nsGeolocationService(); 1002 if (NS_FAILED(result->Init())) { 1003 return nullptr; 1004 } 1005 1006 ClearOnShutdown(&nsGeolocationService::sService); 1007 nsGeolocationService::sService = result; 1008 return result.forget(); 1009 } 1010 1011 void nsGeolocationService::AddLocator(Geolocation* aLocator) { 1012 mGeolocators.AppendElement(aLocator); 1013 } 1014 1015 void nsGeolocationService::RemoveLocator(Geolocation* aLocator) { 1016 mGeolocators.RemoveElement(aLocator); 1017 } 1018 1019 void nsGeolocationService::MoveLocators(nsGeolocationService* aService) { 1020 for (uint32_t i = 0; i < mGeolocators.Length(); i++) { 1021 aService->AddLocator(mGeolocators[i]); 1022 } 1023 } 1024 1025 //////////////////////////////////////////////////// 1026 // Geolocation 1027 //////////////////////////////////////////////////// 1028 1029 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Geolocation) 1030 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 1031 NS_INTERFACE_MAP_ENTRY(nsISupports) 1032 NS_INTERFACE_MAP_ENTRY(nsIGeolocationUpdate) 1033 NS_INTERFACE_MAP_END 1034 1035 NS_IMPL_CYCLE_COLLECTING_ADDREF(Geolocation) 1036 NS_IMPL_CYCLE_COLLECTING_RELEASE(Geolocation) 1037 1038 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Geolocation, mPendingCallbacks, 1039 mWatchingCallbacks, mPendingRequests) 1040 1041 Geolocation::Geolocation() 1042 : mProtocolType(ProtocolType::OTHER), mLastWatchId(1) {} 1043 1044 Geolocation::~Geolocation() { 1045 if (mService) { 1046 Shutdown(); 1047 } 1048 } 1049 1050 StaticRefPtr<Geolocation> Geolocation::sNonWindowSingleton; 1051 1052 already_AddRefed<Geolocation> Geolocation::NonWindowSingleton() { 1053 if (sNonWindowSingleton) { 1054 return do_AddRef(sNonWindowSingleton); 1055 } 1056 1057 RefPtr<Geolocation> result = new Geolocation(); 1058 DebugOnly<nsresult> rv = result->Init(); 1059 MOZ_ASSERT(NS_SUCCEEDED(rv), "How can this fail?"); 1060 1061 ClearOnShutdown(&sNonWindowSingleton); 1062 sNonWindowSingleton = result; 1063 return result.forget(); 1064 } 1065 1066 nsresult Geolocation::Init(nsPIDOMWindowInner* aContentDom) { 1067 nsCOMPtr<Document> doc = aContentDom ? aContentDom->GetExtantDoc() : nullptr; 1068 1069 // Remember the window 1070 if (aContentDom) { 1071 mOwner = do_GetWeakReference(aContentDom); 1072 if (!mOwner) { 1073 return NS_ERROR_FAILURE; 1074 } 1075 1076 // Grab the principal of the document 1077 if (!doc) { 1078 return NS_ERROR_FAILURE; 1079 } 1080 1081 mPrincipal = doc->NodePrincipal(); 1082 // Store the protocol to send via telemetry later. 1083 if (mPrincipal->SchemeIs("http")) { 1084 mProtocolType = ProtocolType::HTTP; 1085 } else if (mPrincipal->SchemeIs("https")) { 1086 mProtocolType = ProtocolType::HTTPS; 1087 } 1088 } 1089 1090 mBrowsingContext = 1091 doc ? RefPtr<BrowsingContext>(doc->GetBrowsingContext()) : nullptr; 1092 1093 // If no aContentDom was passed into us, we are being used 1094 // by chrome/c++ and have no mOwner, no mPrincipal, and no need 1095 // to prompt. 1096 mService = nsGeolocationService::GetGeolocationService(mBrowsingContext); 1097 1098 if (mService) { 1099 mService->AddLocator(this); 1100 } 1101 1102 return NS_OK; 1103 } 1104 1105 void Geolocation::Shutdown() { 1106 // Release all callbacks 1107 mPendingCallbacks.Clear(); 1108 mWatchingCallbacks.Clear(); 1109 1110 if (mService) { 1111 mService->RemoveLocator(this); 1112 mService->UpdateAccuracy(); 1113 } 1114 1115 mService = nullptr; 1116 mPrincipal = nullptr; 1117 } 1118 1119 nsPIDOMWindowInner* Geolocation::GetParentObject() const { 1120 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mOwner); 1121 return window.get(); 1122 } 1123 1124 bool Geolocation::HasActiveCallbacks() { 1125 return mPendingCallbacks.Length() || mWatchingCallbacks.Length(); 1126 } 1127 1128 bool Geolocation::HighAccuracyRequested() { 1129 for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) { 1130 if (mWatchingCallbacks[i]->WantsHighAccuracy()) { 1131 return true; 1132 } 1133 } 1134 1135 for (uint32_t i = 0; i < mPendingCallbacks.Length(); i++) { 1136 if (mPendingCallbacks[i]->WantsHighAccuracy()) { 1137 return true; 1138 } 1139 } 1140 1141 return false; 1142 } 1143 1144 void Geolocation::RemoveRequest(nsGeolocationRequest* aRequest) { 1145 bool requestWasKnown = (mPendingCallbacks.RemoveElement(aRequest) != 1146 mWatchingCallbacks.RemoveElement(aRequest)); 1147 1148 (void)requestWasKnown; 1149 } 1150 1151 NS_IMETHODIMP 1152 Geolocation::Update(nsIDOMGeoPosition* aSomewhere) { 1153 if (!WindowOwnerStillExists()) { 1154 Shutdown(); 1155 return NS_OK; 1156 } 1157 1158 // Don't update position if window is not fully active or the document is 1159 // hidden. We keep the pending callaback and watchers waiting for the next 1160 // update. 1161 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(this->GetOwner()); 1162 if (window) { 1163 nsCOMPtr<Document> document = window->GetDoc(); 1164 bool isHidden = document && document->Hidden(); 1165 if (isHidden || !window->IsFullyActive()) { 1166 return NS_OK; 1167 } 1168 } 1169 1170 if (aSomewhere) { 1171 nsCOMPtr<nsIDOMGeoPositionCoords> coords; 1172 aSomewhere->GetCoords(getter_AddRefs(coords)); 1173 if (coords) { 1174 double accuracy = -1; 1175 coords->GetAccuracy(&accuracy); 1176 glean::geolocation::accuracy.AccumulateSingleSample( 1177 static_cast<uint64_t>(accuracy)); 1178 } 1179 } 1180 1181 for (uint32_t i = mPendingCallbacks.Length(); i > 0; i--) { 1182 mPendingCallbacks[i - 1]->Update(aSomewhere); 1183 RemoveRequest(mPendingCallbacks[i - 1]); 1184 } 1185 1186 // notify everyone that is watching 1187 for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) { 1188 mWatchingCallbacks[i]->Update(aSomewhere); 1189 } 1190 1191 return NS_OK; 1192 } 1193 1194 NS_IMETHODIMP 1195 Geolocation::NotifyError(uint16_t aErrorCode) { 1196 MOZ_LOG(gGeolocationLog, LogLevel::Debug, 1197 ("Geolocation::NotifyError with error code: %u", aErrorCode)); 1198 if (!WindowOwnerStillExists()) { 1199 Shutdown(); 1200 return NS_OK; 1201 } 1202 1203 for (uint32_t i = mPendingCallbacks.Length(); i > 0; i--) { 1204 RefPtr<nsGeolocationRequest> request = mPendingCallbacks[i - 1]; 1205 request->NotifyErrorAndShutdown(aErrorCode); 1206 // NotifyErrorAndShutdown() removes the request from the array 1207 } 1208 1209 // notify everyone that is watching 1210 for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) { 1211 RefPtr<nsGeolocationRequest> request = mWatchingCallbacks[i]; 1212 request->NotifyErrorAndShutdown(aErrorCode); 1213 } 1214 1215 return NS_OK; 1216 } 1217 1218 bool Geolocation::IsFullyActiveOrChrome() { 1219 // For regular content window, only allow this proceed if the window is "fully 1220 // active". 1221 if (nsPIDOMWindowInner* window = this->GetParentObject()) { 1222 return window->IsFullyActive(); 1223 } 1224 // Calls coming from chrome code don't have window, so we can proceed. 1225 return true; 1226 } 1227 1228 bool Geolocation::IsAlreadyCleared(nsGeolocationRequest* aRequest) { 1229 for (uint32_t i = 0, length = mClearedWatchIDs.Length(); i < length; ++i) { 1230 if (mClearedWatchIDs[i] == aRequest->WatchId()) { 1231 return true; 1232 } 1233 } 1234 1235 return false; 1236 } 1237 1238 bool Geolocation::ShouldBlockInsecureRequests() const { 1239 if (Preferences::GetBool(PREF_GEO_SECURITY_ALLOWINSECURE, false)) { 1240 return false; 1241 } 1242 1243 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryReferent(mOwner); 1244 if (!win) { 1245 return false; 1246 } 1247 1248 nsCOMPtr<Document> doc = win->GetDoc(); 1249 if (!doc) { 1250 return false; 1251 } 1252 1253 if (!nsGlobalWindowInner::Cast(win)->IsSecureContext()) { 1254 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns, doc, 1255 nsContentUtils::eDOM_PROPERTIES, 1256 "GeolocationInsecureRequestIsForbidden"); 1257 return true; 1258 } 1259 1260 return false; 1261 } 1262 1263 bool Geolocation::ClearPendingRequest(nsGeolocationRequest* aRequest) { 1264 if (aRequest->IsWatch() && this->IsAlreadyCleared(aRequest)) { 1265 this->NotifyAllowedRequest(aRequest); 1266 this->ClearWatch(aRequest->WatchId()); 1267 return true; 1268 } 1269 1270 return false; 1271 } 1272 1273 void Geolocation::GetCurrentPosition(PositionCallback& aCallback, 1274 PositionErrorCallback* aErrorCallback, 1275 const PositionOptions& aOptions, 1276 CallerType aCallerType, ErrorResult& aRv) { 1277 nsresult rv = GetCurrentPosition( 1278 GeoPositionCallback(&aCallback), GeoPositionErrorCallback(aErrorCallback), 1279 CreatePositionOptionsCopy(aOptions), aCallerType); 1280 1281 if (NS_FAILED(rv)) { 1282 aRv.Throw(rv); 1283 } 1284 } 1285 1286 nsresult Geolocation::GetCurrentPosition(GeoPositionCallback callback, 1287 GeoPositionErrorCallback errorCallback, 1288 UniquePtr<PositionOptions>&& options, 1289 CallerType aCallerType) { 1290 if (!IsFullyActiveOrChrome()) { 1291 RefPtr<GeolocationPositionError> positionError = 1292 new GeolocationPositionError( 1293 this, GeolocationPositionError_Binding::POSITION_UNAVAILABLE); 1294 positionError->NotifyCallback(errorCallback); 1295 return NS_OK; 1296 } 1297 1298 if (mPendingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) { 1299 return NS_ERROR_NOT_AVAILABLE; 1300 } 1301 1302 // After this we hand over ownership of options to our nsGeolocationRequest. 1303 1304 nsIEventTarget* target = GetMainThreadSerialEventTarget(); 1305 RefPtr<nsGeolocationRequest> request = new nsGeolocationRequest( 1306 this, std::move(callback), std::move(errorCallback), std::move(options), 1307 target); 1308 1309 if (!StaticPrefs::geo_enabled() || ShouldBlockInsecureRequests() || 1310 !request->CheckPermissionDelegate()) { 1311 request->RequestDelayedTask(target, 1312 nsGeolocationRequest::DelayedTaskType::Deny); 1313 return NS_OK; 1314 } 1315 1316 if (!mOwner && aCallerType != CallerType::System) { 1317 return NS_ERROR_FAILURE; 1318 } 1319 1320 if (mOwner) { 1321 RequestIfPermitted(request); 1322 return NS_OK; 1323 } 1324 1325 if (aCallerType != CallerType::System) { 1326 return NS_ERROR_FAILURE; 1327 } 1328 1329 request->RequestDelayedTask(target, 1330 nsGeolocationRequest::DelayedTaskType::Allow); 1331 1332 return NS_OK; 1333 } 1334 1335 int32_t Geolocation::WatchPosition(PositionCallback& aCallback, 1336 PositionErrorCallback* aErrorCallback, 1337 const PositionOptions& aOptions, 1338 CallerType aCallerType, ErrorResult& aRv) { 1339 return WatchPosition(GeoPositionCallback(&aCallback), 1340 GeoPositionErrorCallback(aErrorCallback), 1341 CreatePositionOptionsCopy(aOptions), aCallerType, aRv); 1342 } 1343 1344 int32_t Geolocation::WatchPosition( 1345 nsIDOMGeoPositionCallback* aCallback, 1346 nsIDOMGeoPositionErrorCallback* aErrorCallback, 1347 UniquePtr<PositionOptions>&& aOptions) { 1348 MOZ_ASSERT(aCallback); 1349 1350 return WatchPosition(GeoPositionCallback(aCallback), 1351 GeoPositionErrorCallback(aErrorCallback), 1352 std::move(aOptions), CallerType::System, IgnoreErrors()); 1353 } 1354 1355 // On errors we return 0 because that's not a valid watch id and will 1356 // get ignored in clearWatch. 1357 int32_t Geolocation::WatchPosition(GeoPositionCallback aCallback, 1358 GeoPositionErrorCallback aErrorCallback, 1359 UniquePtr<PositionOptions>&& aOptions, 1360 CallerType aCallerType, ErrorResult& aRv) { 1361 if (!IsFullyActiveOrChrome()) { 1362 RefPtr<GeolocationPositionError> positionError = 1363 new GeolocationPositionError( 1364 this, GeolocationPositionError_Binding::POSITION_UNAVAILABLE); 1365 positionError->NotifyCallback(aErrorCallback); 1366 return 0; 1367 } 1368 1369 if (mWatchingCallbacks.Length() > MAX_GEO_REQUESTS_PER_WINDOW) { 1370 aRv.Throw(NS_ERROR_NOT_AVAILABLE); 1371 return 0; 1372 } 1373 1374 // The watch ID: 1375 int32_t watchId = mLastWatchId++; 1376 1377 nsIEventTarget* target = GetMainThreadSerialEventTarget(); 1378 RefPtr<nsGeolocationRequest> request = new nsGeolocationRequest( 1379 this, std::move(aCallback), std::move(aErrorCallback), 1380 std::move(aOptions), target, true, watchId); 1381 1382 if (!StaticPrefs::geo_enabled() || ShouldBlockInsecureRequests() || 1383 !request->CheckPermissionDelegate()) { 1384 request->RequestDelayedTask(target, 1385 nsGeolocationRequest::DelayedTaskType::Deny); 1386 return watchId; 1387 } 1388 1389 if (!mOwner && aCallerType != CallerType::System) { 1390 aRv.Throw(NS_ERROR_FAILURE); 1391 return 0; 1392 } 1393 1394 if (mOwner) { 1395 RequestIfPermitted(request); 1396 return watchId; 1397 } 1398 1399 if (aCallerType != CallerType::System) { 1400 aRv.Throw(NS_ERROR_FAILURE); 1401 return 0; 1402 } 1403 1404 request->Allow(JS::UndefinedHandleValue); 1405 return watchId; 1406 } 1407 1408 void Geolocation::ClearWatch(int32_t aWatchId) { 1409 if (aWatchId < 1) { 1410 return; 1411 } 1412 1413 if (!mClearedWatchIDs.Contains(aWatchId)) { 1414 mClearedWatchIDs.AppendElement(aWatchId); 1415 } 1416 1417 for (uint32_t i = 0, length = mWatchingCallbacks.Length(); i < length; ++i) { 1418 if (mWatchingCallbacks[i]->WatchId() == aWatchId) { 1419 mWatchingCallbacks[i]->Shutdown(); 1420 RemoveRequest(mWatchingCallbacks[i]); 1421 mClearedWatchIDs.RemoveElement(aWatchId); 1422 break; 1423 } 1424 } 1425 1426 // make sure we also search through the pending requests lists for 1427 // watches to clear... 1428 for (uint32_t i = 0, length = mPendingRequests.Length(); i < length; ++i) { 1429 if (mPendingRequests[i]->IsWatch() && 1430 (mPendingRequests[i]->WatchId() == aWatchId)) { 1431 mPendingRequests[i]->Shutdown(); 1432 mPendingRequests.RemoveElementAt(i); 1433 break; 1434 } 1435 } 1436 } 1437 1438 bool Geolocation::WindowOwnerStillExists() { 1439 // an owner was never set when Geolocation 1440 // was created, which means that this object 1441 // is being used without a window. 1442 if (mOwner == nullptr) { 1443 return true; 1444 } 1445 1446 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mOwner); 1447 1448 if (window) { 1449 nsPIDOMWindowOuter* outer = window->GetOuterWindow(); 1450 if (!outer || outer->GetCurrentInnerWindow() != window || outer->Closed()) { 1451 return false; 1452 } 1453 } 1454 1455 return true; 1456 } 1457 1458 void Geolocation::NotifyAllowedRequest(nsGeolocationRequest* aRequest) { 1459 if (aRequest->IsWatch()) { 1460 mWatchingCallbacks.AppendElement(aRequest); 1461 } else { 1462 mPendingCallbacks.AppendElement(aRequest); 1463 } 1464 } 1465 1466 /* static */ bool Geolocation::RegisterRequestWithPrompt( 1467 nsGeolocationRequest* request) { 1468 nsIEventTarget* target = GetMainThreadSerialEventTarget(); 1469 ContentPermissionRequestBase::PromptResult pr = request->CheckPromptPrefs(); 1470 if (pr == ContentPermissionRequestBase::PromptResult::Granted) { 1471 request->RequestDelayedTask(target, 1472 nsGeolocationRequest::DelayedTaskType::Allow); 1473 return true; 1474 } 1475 if (pr == ContentPermissionRequestBase::PromptResult::Denied) { 1476 request->RequestDelayedTask(target, 1477 nsGeolocationRequest::DelayedTaskType::Deny); 1478 return true; 1479 } 1480 1481 request->RequestDelayedTask(target, 1482 nsGeolocationRequest::DelayedTaskType::Request); 1483 return true; 1484 } 1485 1486 /* static */ geolocation::SystemGeolocationPermissionBehavior 1487 Geolocation::GetLocationOSPermission() { 1488 auto permission = geolocation::GetGeolocationPermissionBehavior(); 1489 1490 if (!StaticPrefs::geo_prompt_open_system_prefs() && 1491 permission == geolocation::SystemGeolocationPermissionBehavior:: 1492 GeckoWillPromptUser) { 1493 return geolocation::SystemGeolocationPermissionBehavior::NoPrompt; 1494 } 1495 return permission; 1496 } 1497 1498 void Geolocation::RequestIfPermitted(nsGeolocationRequest* request) { 1499 auto getPermission = [request = RefPtr{request}](auto aPermission) { 1500 switch (aPermission) { 1501 case geolocation::SystemGeolocationPermissionBehavior:: 1502 SystemWillPromptUser: 1503 case geolocation::SystemGeolocationPermissionBehavior:: 1504 GeckoWillPromptUser: 1505 request->SetPromptBehavior(aPermission); 1506 break; 1507 case geolocation::SystemGeolocationPermissionBehavior::NoPrompt: 1508 // Either location access is already permitted by OS or the system 1509 // permission UX is not available for this platform. Do nothing. 1510 break; 1511 default: 1512 MOZ_ASSERT_UNREACHABLE( 1513 "unexpected GeolocationPermissionBehavior value"); 1514 break; 1515 } 1516 RegisterRequestWithPrompt(request); 1517 }; 1518 1519 if (auto* contentChild = ContentChild::GetSingleton()) { 1520 contentChild->SendGetSystemGeolocationPermissionBehavior( 1521 std::move(getPermission), 1522 [request = 1523 RefPtr{request}](mozilla::ipc::ResponseRejectReason aReason) { 1524 NS_WARNING("Error sending GetSystemGeolocationPermissionBehavior"); 1525 // We still need to run the location request, even if we don't 1526 // have permission. 1527 RegisterRequestWithPrompt(request); 1528 }); 1529 } else { 1530 MOZ_ASSERT(XRE_IsParentProcess()); 1531 getPermission(GetLocationOSPermission()); 1532 } 1533 } 1534 1535 JSObject* Geolocation::WrapObject(JSContext* aCtx, 1536 JS::Handle<JSObject*> aGivenProto) { 1537 return Geolocation_Binding::Wrap(aCtx, this, aGivenProto); 1538 }