/*
 * Copyright (C) 2008, 2009, 2010, 2011 Apple Inc. All Rights Reserved.
 * Copyright 2010, The Android Open Source Project
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_GEOLOCATION_GEOLOCATION_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_GEOLOCATION_GEOLOCATION_H_

#include "base/dcheck_is_on.h"
#include "base/types/expected.h"
#include "services/device/public/mojom/geolocation.mojom-blink.h"
#include "third_party/blink/public/mojom/geolocation/geolocation_service.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_position_callback.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_position_error_callback.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_position_options.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
#include "third_party/blink/renderer/core/geolocation/geo_notifier.h"
#include "third_party/blink/renderer/core/geolocation/geolocation_position_error.h"
#include "third_party/blink/renderer/core/geolocation/geolocation_watchers.h"
#include "third_party/blink/renderer/core/geolocation/geoposition.h"
#include "third_party/blink/renderer/core/page/page_visibility_observer.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
#include "third_party/blink/renderer/platform/supplementable.h"
#include "third_party/blink/renderer/platform/timer.h"

namespace blink {

namespace mojom {
enum class PermissionStatus;
}  // namespace mojom

class LocalFrame;
class Navigator;

class CORE_EXPORT Geolocation final
    : public ScriptWrappable,
      public ActiveScriptWrappable<Geolocation>,
      public Supplement<Navigator>,
      public ExecutionContextLifecycleObserver,
      public PageVisibilityObserver {
  DEFINE_WRAPPERTYPEINFO();

 public:
  static const char kSupplementName[];
  static Geolocation* geolocation(Navigator&);

  explicit Geolocation(Navigator&);
  ~Geolocation() override;
  void Trace(Visitor*) const override;

  // Inherited from ExecutionContextLifecycleObserver and
  // PageVisibilityObserver.
  void ContextDestroyed() override;

  LocalFrame* GetFrame() const;

  // Creates a oneshot and attempts to obtain a position that meets the
  // constraints of the options. This method gets called when the geolocation
  // API is invoked from script.
  void getCurrentPositionForBindings(
      V8PositionCallback*,
      V8PositionErrorCallback* = nullptr,
      const PositionOptions* = PositionOptions::Create());

  // Creates a oneshot and attempts to obtain a position that meets the
  // constraints of the options. This method gets called when the geolocation
  // API is invoked from Blink.
  void GetCurrentPosition(
      base::RepeatingCallback<
          void(base::expected<Geoposition*, GeolocationPositionError*>)>,
      const PositionOptions* = PositionOptions::Create());

  // Creates a watcher that will be notified whenever a new position is
  // available that meets the constraints of the options. This method gets
  // called when the geolocation API is invoked from script.
  int watchPositionForBindings(
      V8PositionCallback*,
      V8PositionErrorCallback* = nullptr,
      const PositionOptions* = PositionOptions::Create());

  // Creates a watcher that will be notified whenever a new position is
  // available that meets the constraints of the options. This method gets
  // called when the geolocation API is invoked from Blink.
  // Returns an watch ID, which can be used to clear the watcher.
  int WatchPosition(
      base::RepeatingCallback<
          void(base::expected<Geoposition*, GeolocationPositionError*>)>,
      const PositionOptions* = PositionOptions::Create());

  int WatchPositionInternal(GeoNotifier* notifier);

  // Removes all references to the watcher, it will not be updated again.
  void clearWatch(int watch_id);

  // Notifies this that a new position is available. Must never be called
  // before permission is granted by the user.
  void PositionChanged();

  // Discards the notifier because a fatal error occurred for it.
  void FatalErrorOccurred(GeoNotifier*);

  // Adds the notifier to the set awaiting a cached position. Runs the success
  // callbacks for them if permission has been granted. Requests permission if
  // it is unknown.
  void RequestUsesCachedPosition(GeoNotifier*);

  // Discards the notifier if it is a oneshot because it timed it.
  void RequestTimedOut(GeoNotifier*);

  // Returns true if this geolocation still owns the given notifier.
  bool DoesOwnNotifier(GeoNotifier*) const;

  // Inherited from PageVisibilityObserver.
  void PageVisibilityChanged() override;

  // TODO(yukishiino): This is a short-term speculative fix for
  // crbug.com/792604. Remove this once the bug is fixed.
  bool HasPendingActivity() const final;

 private:
  // Customized HeapHashSet class that checks notifiers' timers. Notifier's
  // timer may be active only when the notifier is owned by the Geolocation.
  class GeoNotifierSet final : public GarbageCollected<GeoNotifierSet> {
   public:
    void Trace(Visitor* visitor) const { visitor->Trace(set_); }

    auto begin() const { return set_.begin(); }
    auto end() const { return set_.end(); }
    auto size() const { return set_.size(); }

    auto insert(GeoNotifier* value) {
      DCHECK(!value->IsTimerActive());
      return set_.insert(value);
    }

    void erase(GeoNotifier* value) {
      DCHECK(!value->IsTimerActive());
      return set_.erase(value);
    }

    void clear() {
#if DCHECK_IS_ON()
      for (const auto& notifier : set_) {
        DCHECK(!notifier->IsTimerActive());
      }
#endif
      set_.clear();
    }

    auto Contains(GeoNotifier* value) const { return set_.Contains(value); }
    auto IsEmpty() const { return set_.empty(); }

    auto InsertWithoutTimerCheck(GeoNotifier* value) {
      return set_.insert(value);
    }
    void ClearWithoutTimerCheck() { set_.clear(); }

   private:
    HeapHashSet<Member<GeoNotifier>> set_;
  };

  bool HasListeners() const {
    return !one_shots_->IsEmpty() || !watchers_->IsEmpty();
  }

  void StopTimers();
  void StartTimers();

  // Runs the success callbacks on all notifiers. A position must be available
  // and the user must have given permission.
  void MakeSuccessCallbacks();

  // and also  clears the watchers if the error is fatal.
  void HandleError(GeolocationPositionError*);

  // Ensures location updates are active and accuracy is
  // always configured based on the current set of requests. This central
  // function should be called whenever active notifiers change or permission is
  // granted.
  // If the connection to the Geolocation service is not yet established, it
  // initiates the connection and permission request, then returns early. This
  // function is called again once the permission status is resolved.
  void UpdateGeolocationState();

  // Tears down the connection to the Geolocation service and stops all timers.
  void StopUpdating();

  // Establishes a connection to the Geolocation service and request permission.
  // This is an asynchronous operation. Returns true if the connection is
  // already established. Returns false if the connection is not yet
  // established, In this case, permission will be reuested and
  // OnGeolocationPermissionStatusUpdated will be called upon completion.
  bool EnsureGeolocationConnection();

  // Resets the connection to the Geolocation service.
  void ResetGeolocationConnection();
  void QueryNextPosition();

  // Attempts to obtain a position for the given notifier, either by using
  // the cached position or by requesting one from the Geolocation service.
  // Sets a fatal error if permission is denied or no position can be
  // obtained.
  void StartRequest(GeoNotifier*);

  bool HaveSuitableCachedPosition(const PositionOptions*);

  // Record whether the origin trying to access Geolocation would be
  // allowed to access a feature that can only be accessed by secure origins.
  // See https://goo.gl/Y0ZkNV
  void RecordOriginTypeAccess() const;

  void OnPositionUpdated(device::mojom::blink::GeopositionResultPtr);

  void OnGeolocationConnectionError();

  // Callback for the asynchronous permission request. Upon invoked, it proceeds
  // with location updates or handles the error.
  void OnGeolocationPermissionStatusUpdated(mojom::blink::PermissionStatus);

  void HandlePermissionError();

  void UpdateAccuracyHint();

  mojom::blink::GeolocationAccuracy GetAccuracyLevel() const;

  Member<GeoNotifierSet> one_shots_;
  Member<GeolocationWatchers> watchers_;
  // GeoNotifiers that are in the middle of invocation.
  //
  // |HandleError(error)| and |MakeSuccessCallbacks| need to clear |one_shots_|
  // (and optionally |watchers_|) before invoking the callbacks, in order to
  // avoid clearing notifiers added by calls to Geolocation methods
  // from the callbacks. Thus, something else needs to make the notifiers being
  // invoked alive with wrapper-tracing because V8 GC may run during the
  // callbacks. |one_shots_being_invoked_| and |watchers_being_invoked_| perform
  // wrapper-tracing.
  // TODO(https://crbug.com/796145): Remove this hack once on-stack objects
  // get supported by either of wrapper-tracing or unified GC.
  Member<GeoNotifierSet> one_shots_being_invoked_;
  HeapVector<Member<GeoNotifier>> watchers_being_invoked_;
  Member<Geoposition> last_position_;

  HeapMojoRemote<device::mojom::blink::Geolocation> geolocation_;
  HeapMojoRemote<mojom::blink::GeolocationService> geolocation_service_;
  // `enable_high_accuracy_` tracks whether any active request has opted into
  // high accuracy. This is used as a hint for the location provider to balance
  // power consumption vs. accuracy.
  bool enable_high_accuracy_ = false;

  // `accuracy_` represents the aggregated accuracy level requested by all
  // active listeners. If any request specifies `kApproximate`, the service
  // level is downgraded to approximate to respect privacy constraints.
  // Note: `accuracy_` is only used when the
  // `ApproximateGeolocationWebVisibleAPI` flag is enabled.
  mojom::blink::GeolocationAccuracy accuracy_ =
      mojom::blink::GeolocationAccuracy::kApproximate;

  // Whether a QueryNextPosition request sent and we are waiting for a position
  // update.
  bool updating_ = false;
  bool permission_request_in_progress_ = false;
};

}  // namespace blink

#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_GEOLOCATION_GEOLOCATION_H_
