tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }