blob: 32bbb76f92c11c0e0a84718094babfa95a19e361 [file]
/*
* Copyright (C) 2008, 2009, 2010, 2011 Apple Inc. All Rights Reserved.
* Copyright (C) 2009 Torch Mobile, Inc.
* 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.
*/
#include "third_party/blink/renderer/core/geolocation/geolocation.h"
#include <algorithm>
#include <optional>
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "services/device/public/mojom/geoposition.mojom-blink.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-blink.h"
#include "third_party/blink/public/platform/browser_interface_broker_proxy.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_accuracy_mode.h"
#include "third_party/blink/renderer/core/frame/deprecation/deprecation.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/navigator.h"
#include "third_party/blink/renderer/core/frame/performance_monitor.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/geolocation/geo_notifier_blink.h"
#include "third_party/blink/renderer/core/geolocation/geo_notifier_v8.h"
#include "third_party/blink/renderer/core/geolocation/geolocation_coordinates.h"
#include "third_party/blink/renderer/core/geolocation/geolocation_error.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/timing/epoch_time_stamp.h"
#include "third_party/blink/renderer/platform/bindings/source_location.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
namespace blink {
namespace {
using AccuracyMode = V8AccuracyMode::Enum;
const char kPermissionDeniedErrorMessage[] = "User denied Geolocation";
const char kFeaturePolicyErrorMessage[] =
"Geolocation has been disabled in this document by permissions policy.";
const char kFeaturePolicyConsoleWarning[] =
"Geolocation access has been blocked because of a permissions policy "
"applied to the current document. See https://crbug.com/414348233 for more "
"details.";
Geoposition* CreateGeoposition(
const device::mojom::blink::Geoposition& position) {
auto* coordinates = MakeGarbageCollected<GeolocationCoordinates>(
position.latitude, position.longitude,
// Lowest point on land is at approximately -400 meters.
position.altitude > -10000. ? std::make_optional(position.altitude)
: std::nullopt,
position.accuracy,
position.altitude_accuracy >= 0.
? std::make_optional(position.altitude_accuracy)
: std::nullopt,
position.heading >= 0. && position.heading <= 360.
? std::make_optional(position.heading)
: std::nullopt,
position.speed >= 0. ? std::optional(position.speed) : std::nullopt);
return MakeGarbageCollected<Geoposition>(
coordinates, ConvertTimeToEpochTimeStamp(position.timestamp));
}
GeolocationPositionError* CreatePositionError(
const device::mojom::blink::GeopositionError& error) {
GeolocationPositionError::ErrorCode error_code =
GeolocationPositionError::kPositionUnavailable;
switch (error.error_code) {
case device::mojom::blink::GeopositionErrorCode::kPermissionDenied:
error_code = GeolocationPositionError::kPermissionDenied;
break;
case device::mojom::blink::GeopositionErrorCode::kPositionUnavailable:
error_code = GeolocationPositionError::kPositionUnavailable;
break;
default:
// On the Blink side, it should only handle W3C-defined error codes. If it
// reaches here, that means a platform-specific error type is being
// propagated to Blink. We will now just use kPositionUnavailable until
// more explicit error codes are defined in the W3C spec.
error_code = GeolocationPositionError::kPositionUnavailable;
break;
}
return MakeGarbageCollected<GeolocationPositionError>(error_code,
error.error_message);
}
static void ReportGeolocationViolation(LocalDOMWindow* window) {
// TODO(dcheng): |doc| probably can't be null here.
if (!LocalFrame::HasTransientUserActivation(window ? window->GetFrame()
: nullptr)) {
PerformanceMonitor::ReportGenericViolation(
window, PerformanceMonitor::kDiscouragedAPIUse,
"Only request geolocation information in response to a user gesture.",
base::TimeDelta(), nullptr);
}
}
bool ValidateGeoposition(const device::mojom::blink::Geoposition& position) {
return position.latitude >= -90. && position.latitude <= 90. &&
position.longitude >= -180. && position.longitude <= 180. &&
position.accuracy >= 0. && !position.timestamp.is_null();
}
#if BUILDFLAG(IS_ANDROID)
// On Android, `enableHighAccuracy` previously always returned a precise
// location. With the `ApproximateGeolocationPermissionEnabled` feature, the
// location provider reuses the same accuracy setting but can return precise or
// approximate locations. To ensure consistent accuracy for sites before and
// after this feature, we override the accuracy setting here.
PositionOptions* OverrideAccuracyHint(const PositionOptions* options) {
PositionOptions* copied_options = PositionOptions::Create();
copied_options->setTimeout(options->timeout());
copied_options->setMaximumAge(options->maximumAge());
copied_options->setEnableHighAccuracy(true);
if (RuntimeEnabledFeatures::ApproximateGeolocationWebVisibleAPIEnabled()) {
copied_options->setAccuracyMode(options->accuracyMode());
}
return copied_options;
}
#endif // BUILDFLAG(IS_ANDROID)
} // namespace
// static
const char Geolocation::kSupplementName[] = "Geolocation";
// static
Geolocation* Geolocation::geolocation(Navigator& navigator) {
if (!navigator.DomWindow())
return nullptr;
Geolocation* supplement = Supplement<Navigator>::From<Geolocation>(navigator);
if (!supplement) {
supplement = MakeGarbageCollected<Geolocation>(navigator);
ProvideTo(navigator, supplement);
}
return supplement;
}
Geolocation::Geolocation(Navigator& navigator)
: ActiveScriptWrappable<Geolocation>({}),
Supplement<Navigator>(navigator),
ExecutionContextLifecycleObserver(navigator.DomWindow()),
PageVisibilityObserver(navigator.DomWindow()->GetFrame()->GetPage()),
one_shots_(MakeGarbageCollected<GeoNotifierSet>()),
watchers_(MakeGarbageCollected<GeolocationWatchers>()),
one_shots_being_invoked_(MakeGarbageCollected<GeoNotifierSet>()),
geolocation_(navigator.DomWindow()),
geolocation_service_(navigator.DomWindow()) {}
Geolocation::~Geolocation() = default;
void Geolocation::Trace(Visitor* visitor) const {
visitor->Trace(one_shots_);
visitor->Trace(watchers_);
visitor->Trace(one_shots_being_invoked_);
visitor->Trace(watchers_being_invoked_);
visitor->Trace(last_position_);
visitor->Trace(geolocation_);
visitor->Trace(geolocation_service_);
ScriptWrappable::Trace(visitor);
Supplement<Navigator>::Trace(visitor);
ExecutionContextLifecycleObserver::Trace(visitor);
PageVisibilityObserver::Trace(visitor);
}
LocalFrame* Geolocation::GetFrame() const {
return DomWindow() ? DomWindow()->GetFrame() : nullptr;
}
void Geolocation::ContextDestroyed() {
StopTimers();
one_shots_->clear();
watchers_->Clear();
StopUpdating();
last_position_ = nullptr;
}
void Geolocation::RecordOriginTypeAccess() const {
DCHECK(GetFrame());
LocalDOMWindow* window = DomWindow();
// It is required by isSecureContext() but isn't actually used. This could be
// used later if a warning is shown in the developer console.
String insecure_origin_msg;
if (window->IsSecureContext(insecure_origin_msg)) {
UseCounter::Count(window, WebFeature::kGeolocationSecureOrigin);
window->CountUseOnlyInCrossOriginIframe(
WebFeature::kGeolocationSecureOriginIframe);
} else if (GetFrame()
->GetSettings()
->GetAllowGeolocationOnInsecureOrigins()) {
// Android WebView allows geolocation in secure contexts for legacy apps.
// See https://crbug.com/603574 for details.
Deprecation::CountDeprecation(
window, WebFeature::kGeolocationInsecureOriginDeprecatedNotRemoved);
Deprecation::CountDeprecationCrossOriginIframe(
window,
WebFeature::kGeolocationInsecureOriginIframeDeprecatedNotRemoved);
} else {
Deprecation::CountDeprecation(window,
WebFeature::kGeolocationInsecureOrigin);
Deprecation::CountDeprecationCrossOriginIframe(
window, WebFeature::kGeolocationInsecureOriginIframe);
}
}
void Geolocation::getCurrentPositionForBindings(
V8PositionCallback* success_callback,
V8PositionErrorCallback* error_callback,
const PositionOptions* v8_options) {
const PositionOptions* options =
#if BUILDFLAG(IS_ANDROID)
RuntimeEnabledFeatures::ApproximateGeolocationPermissionEnabled()
? OverrideAccuracyHint(v8_options)
: v8_options;
#else
v8_options;
#endif
if (options->enableHighAccuracy()) {
UseCounter::Count(GetExecutionContext(),
WebFeature::kGeolocationGetCurrentPositionHighAccuracy);
}
if (RuntimeEnabledFeatures::ApproximateGeolocationWebVisibleAPIEnabled() &&
options->accuracyMode().AsEnum() == V8AccuracyMode::Enum::kApproximate) {
UseCounter::Count(GetExecutionContext(),
WebFeature::kGeolocationAccuracyModeApproximate);
}
if (!GetFrame())
return;
probe::BreakableLocation(GetExecutionContext(),
"Geolocation.getCurrentPosition");
auto* notifier = MakeGarbageCollected<GeoNotifierV8>(
this, options, success_callback, error_callback);
one_shots_->insert(notifier);
StartRequest(notifier);
}
void Geolocation::GetCurrentPosition(
base::RepeatingCallback<
void(base::expected<Geoposition*, GeolocationPositionError*>)> callback,
const PositionOptions* options) {
if (!GetFrame()) {
return;
}
auto* notifier =
MakeGarbageCollected<GeoNotifierBlink>(this, options, callback);
one_shots_->insert(notifier);
StartRequest(notifier);
}
int Geolocation::watchPositionForBindings(
V8PositionCallback* success_callback,
V8PositionErrorCallback* error_callback,
const PositionOptions* v8_options) {
const PositionOptions* options =
#if BUILDFLAG(IS_ANDROID)
RuntimeEnabledFeatures::ApproximateGeolocationPermissionEnabled()
? OverrideAccuracyHint(v8_options)
: v8_options;
#else
v8_options;
#endif
if (options->enableHighAccuracy()) {
UseCounter::Count(GetExecutionContext(),
WebFeature::kGeolocationWatchPositionHighAccuracy);
}
if (RuntimeEnabledFeatures::ApproximateGeolocationWebVisibleAPIEnabled() &&
options->accuracyMode().AsEnum() == V8AccuracyMode::Enum::kApproximate) {
UseCounter::Count(GetExecutionContext(),
WebFeature::kGeolocationAccuracyModeApproximate);
}
if (!GetFrame())
return 0;
probe::BreakableLocation(GetExecutionContext(), "Geolocation.watchPosition");
auto* notifier = MakeGarbageCollected<GeoNotifierV8>(
this, options, success_callback, error_callback);
return WatchPositionInternal(notifier);
}
int Geolocation::WatchPosition(
base::RepeatingCallback<
void(base::expected<Geoposition*, GeolocationPositionError*>)> callback,
const PositionOptions* options) {
if (!GetFrame()) {
return 0;
}
auto* notifier =
MakeGarbageCollected<GeoNotifierBlink>(this, options, callback);
return WatchPositionInternal(notifier);
}
int Geolocation::WatchPositionInternal(GeoNotifier* notifier) {
int watch_id;
// Keep asking for the next id until we're given one that we don't already
// have.
do {
watch_id = GetExecutionContext()->CircularSequentialID();
} while (!watchers_->Add(watch_id, notifier));
StartRequest(notifier);
return watch_id;
}
void Geolocation::StartRequest(GeoNotifier* notifier) {
RecordOriginTypeAccess();
String error_message;
if (!GetFrame()->GetSettings()->GetAllowGeolocationOnInsecureOrigins() &&
!GetExecutionContext()->IsSecureContext(error_message)) {
notifier->SetFatalError(MakeGarbageCollected<GeolocationPositionError>(
GeolocationPositionError::kPermissionDenied, error_message));
return;
}
if (!GetExecutionContext()->IsFeatureEnabled(
network::mojom::PermissionsPolicyFeature::kGeolocation,
ReportOptions::kReportOnFailure, kFeaturePolicyConsoleWarning)) {
UseCounter::Count(GetExecutionContext(),
WebFeature::kGeolocationDisabledByFeaturePolicy);
notifier->SetFatalError(MakeGarbageCollected<GeolocationPositionError>(
GeolocationPositionError::kPermissionDenied,
kFeaturePolicyErrorMessage));
return;
}
ReportGeolocationViolation(DomWindow());
if (HaveSuitableCachedPosition(notifier->Options())) {
notifier->SetUseCachedPosition();
return;
}
notifier->StartTimer();
UpdateGeolocationState();
}
void Geolocation::FatalErrorOccurred(GeoNotifier* notifier) {
DCHECK(!notifier->IsTimerActive());
// This request has failed fatally. Remove it from our lists.
one_shots_->erase(notifier);
watchers_->Remove(notifier);
if (!HasListeners())
StopUpdating();
}
void Geolocation::RequestUsesCachedPosition(GeoNotifier* notifier) {
DCHECK(!notifier->IsTimerActive());
notifier->RunSuccessCallback(last_position_);
// If this is a one-shot request, stop it. Otherwise, if the watch still
// exists, start the service to get updates.
if (one_shots_->Contains(notifier)) {
one_shots_->erase(notifier);
}
if (HasListeners()) {
UpdateGeolocationState();
} else {
StopUpdating();
}
}
void Geolocation::RequestTimedOut(GeoNotifier* notifier) {
DCHECK(!notifier->IsTimerActive());
// If this is a one-shot request, stop it.
one_shots_->erase(notifier);
if (!HasListeners())
StopUpdating();
}
bool Geolocation::DoesOwnNotifier(GeoNotifier* notifier) const {
return one_shots_->Contains(notifier) ||
one_shots_being_invoked_->Contains(notifier) ||
watchers_->Contains(notifier) ||
watchers_being_invoked_.Contains(notifier);
}
bool Geolocation::HaveSuitableCachedPosition(const PositionOptions* options) {
if (!last_position_)
return false;
EpochTimeStamp current_time_millis =
ConvertTimeToEpochTimeStamp(base::Time::Now());
bool is_last_position_suitable =
options->maximumAge() &&
last_position_->timestamp() > current_time_millis - options->maximumAge();
if (!is_last_position_suitable && !watchers_->IsEmpty()) {
UseCounter::Count(
DomWindow(),
WebFeature::
kGeolocationRequestPositionWithPotentiallyUpToDateWatchedCachedPosition);
}
return is_last_position_suitable;
}
void Geolocation::clearWatch(int watch_id) {
if (watch_id <= 0)
return;
GeoNotifier* notifier = watchers_->Find(watch_id);
if (!notifier)
return;
notifier->StopTimer();
watchers_->Remove(watch_id);
if (!HasListeners())
StopUpdating();
}
void Geolocation::StopTimers() {
for (const auto& notifier : *one_shots_) {
notifier->StopTimer();
}
for (const auto& notifier : watchers_->Notifiers()) {
notifier->StopTimer();
}
}
void Geolocation::HandleError(GeolocationPositionError* error) {
DCHECK(error);
DCHECK(one_shots_being_invoked_->IsEmpty());
DCHECK(watchers_being_invoked_.empty());
if (error->IsFatal()) {
// Stop the timers of |one_shots_| and |watchers_| before swapping/copying
// them.
StopTimers();
}
// Set |one_shots_being_invoked_| and |watchers_being_invoked_| to the
// callbacks to be invoked, which must not change during invocation of
// the callbacks. Note that |one_shots_| and |watchers_| might be changed
// by a callback through getCurrentPosition, watchPosition, and/or
// clearWatch.
swap(one_shots_, one_shots_being_invoked_);
watchers_->CopyNotifiersToVector(watchers_being_invoked_);
if (error->IsFatal()) {
// Clear the watchers before invoking the callbacks.
watchers_->Clear();
}
// Invoke the callbacks. Do not send a non-fatal error to the notifiers
// that only need a cached position. Let them receive a cached position
// later.
//
// A notifier may call |clearWatch|, and in that case, that watcher notifier
// already scheduled must be immediately cancelled according to the spec. But
// the current implementation doesn't support such case.
// TODO(mattreynolds): Support watcher cancellation inside notifier callbacks.
for (auto& notifier : *one_shots_being_invoked_) {
if (error->IsFatal() || !notifier->UseCachedPosition())
notifier->RunErrorCallback(error);
}
for (auto& notifier : watchers_being_invoked_) {
if (error->IsFatal() || !notifier->UseCachedPosition())
notifier->RunErrorCallback(error);
}
// |HasListeners| doesn't distinguish those notifiers which require a fresh
// position from those which are okay with a cached position. Perform the
// check before adding the latter back to |one_shots_|.
if (!HasListeners())
StopUpdating();
if (!error->IsFatal()) {
// Keep the notifiers that are okay with a cached position in |one_shots_|.
for (const auto& notifier : *one_shots_being_invoked_) {
if (notifier->UseCachedPosition())
one_shots_->InsertWithoutTimerCheck(notifier.Get());
else
notifier->StopTimer();
}
one_shots_being_invoked_->ClearWithoutTimerCheck();
}
one_shots_being_invoked_->clear();
watchers_being_invoked_.clear();
}
void Geolocation::MakeSuccessCallbacks() {
DCHECK(last_position_);
DCHECK(one_shots_being_invoked_->IsEmpty());
DCHECK(watchers_being_invoked_.empty());
// Set |one_shots_being_invoked_| and |watchers_being_invoked_| to the
// callbacks to be invoked, which must not change during invocation of
// the callbacks. Note that |one_shots_| and |watchers_| might be changed
// by a callback through getCurrentPosition, watchPosition, and/or
// clearWatch.
swap(one_shots_, one_shots_being_invoked_);
watchers_->CopyNotifiersToVector(watchers_being_invoked_);
// Invoke the callbacks.
//
// A notifier may call |clearWatch|, and in that case, that watcher notifier
// already scheduled must be immediately cancelled according to the spec. But
// the current implementation doesn't support such case.
// TODO(mattreynolds): Support watcher cancellation inside notifier callbacks.
for (auto& notifier : *one_shots_being_invoked_)
notifier->RunSuccessCallback(last_position_);
for (auto& notifier : watchers_being_invoked_)
notifier->RunSuccessCallback(last_position_);
one_shots_being_invoked_->clear();
watchers_being_invoked_.clear();
}
void Geolocation::PositionChanged() {
// Stop all currently running timers.
StopTimers();
MakeSuccessCallbacks();
}
void Geolocation::UpdateGeolocationState() {
if (!EnsureGeolocationConnection() || permission_request_in_progress_) {
// Return early while waiting for asynchronous setup to complete; this
// function will be recalled by `OnGeolocationPermissionStatusUpdated`. The
// accuracy is updated here to ensure the SetHighAccuracyHint Mojo call is
// handled promptly after `GeolocationImpl`'s construction. This prevents
// reporting positions with incorrect accuracy, as location request can
// occur immediately after construction.
UpdateAccuracyHint();
return;
}
UpdateAccuracyHint();
if (!updating_) {
QueryNextPosition();
updating_ = true;
}
}
void Geolocation::StopUpdating() {
updating_ = false;
ResetGeolocationConnection();
accuracy_ = mojom::blink::GeolocationAccuracy::kApproximate;
enable_high_accuracy_ = false;
}
bool Geolocation::EnsureGeolocationConnection() {
if (geolocation_.is_bound()) {
return true;
}
// See https://bit.ly/2S0zRAS for task types.
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
GetExecutionContext()->GetTaskRunner(TaskType::kMiscPlatformAPI);
GetFrame()->GetBrowserInterfaceBroker().GetInterface(
geolocation_service_.BindNewPipeAndPassReceiver(task_runner));
geolocation_service_->CreateGeolocation(
geolocation_.BindNewPipeAndPassReceiver(std::move(task_runner)),
LocalFrame::HasTransientUserActivation(GetFrame()), GetAccuracyLevel(),
blink::BindOnce(&Geolocation::OnGeolocationPermissionStatusUpdated,
WrapWeakPersistent(this)));
geolocation_.set_disconnect_handler(blink::BindOnce(
&Geolocation::OnGeolocationConnectionError, WrapWeakPersistent(this)));
permission_request_in_progress_ = true;
return false;
}
void Geolocation::ResetGeolocationConnection() {
geolocation_.reset();
geolocation_service_.reset();
}
void Geolocation::QueryNextPosition() {
geolocation_->QueryNextPosition(
blink::BindOnce(&Geolocation::OnPositionUpdated, WrapPersistent(this)));
}
void Geolocation::OnPositionUpdated(
device::mojom::blink::GeopositionResultPtr result) {
updating_ = false;
if (!GetExecutionContext()) {
return;
}
if (result->is_position()) {
if (!ValidateGeoposition(*result->get_position())) {
return;
}
last_position_ = CreateGeoposition(*result->get_position());
PositionChanged();
} else {
DCHECK(result->is_error());
const auto& geoposition_error = *result->get_error();
GeolocationPositionError* position_error =
CreatePositionError(geoposition_error);
if (!geoposition_error.error_technical.empty()) {
GetExecutionContext()->AddConsoleMessage(
MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kNetwork,
mojom::blink::ConsoleMessageLevel::kError,
geoposition_error.error_technical));
}
if (position_error->code() == GeolocationPositionError::kPermissionDenied) {
position_error->SetIsFatal(true);
}
HandleError(position_error);
}
if (HasListeners()) {
UpdateGeolocationState();
} else {
StopUpdating();
}
}
void Geolocation::PageVisibilityChanged() {
if (GetPage() && GetPage()->IsPageVisible() && HasListeners()) {
UpdateGeolocationState();
} else {
StopUpdating();
}
}
bool Geolocation::HasPendingActivity() const {
return !one_shots_->IsEmpty() || !one_shots_being_invoked_->IsEmpty() ||
!watchers_->IsEmpty() || !watchers_being_invoked_.empty();
}
void Geolocation::OnGeolocationConnectionError() {
ResetGeolocationConnection();
HandlePermissionError();
}
void Geolocation::OnGeolocationPermissionStatusUpdated(
mojom::blink::PermissionStatus status) {
permission_request_in_progress_ = false;
if (!GetExecutionContext()) {
return;
}
if (status == mojom::blink::PermissionStatus::GRANTED) {
// A watchPosition() request can be canceled while the permission prompt
// is showing. Check that we still have listeners before starting updates.
if (HasListeners()) {
UpdateGeolocationState();
} else {
StopUpdating();
}
} else {
HandlePermissionError();
}
}
void Geolocation::HandlePermissionError() {
auto* error = MakeGarbageCollected<GeolocationPositionError>(
GeolocationPositionError::kPermissionDenied,
kPermissionDeniedErrorMessage);
error->SetIsFatal(true);
HandleError(error);
}
void Geolocation::UpdateAccuracyHint() {
if (RuntimeEnabledFeatures::ApproximateGeolocationWebVisibleAPIEnabled()) {
// When the feature is enabled, we manage accuracy state based on the
// aggregated `accuracyMode` and ignore `enableHighAccuracy` option.
mojom::blink::GeolocationAccuracy new_accuracy = GetAccuracyLevel();
if (new_accuracy != accuracy_) {
accuracy_ = new_accuracy;
const bool high_accuracy =
(accuracy_ == mojom::blink::GeolocationAccuracy::kPrecise);
if (geolocation_.is_bound()) {
geolocation_->SetHighAccuracyHint(high_accuracy);
}
}
} else {
// Legacy behavior: accuracy hint is driven solely by the
// `enableHighAccuracy` option.
auto wants_high_accuracy = [](const auto& notifier) {
return notifier->Options()->enableHighAccuracy();
};
const bool enable_high_accuracy =
std::ranges::any_of(*one_shots_, wants_high_accuracy) ||
std::ranges::any_of(watchers_->Notifiers(), wants_high_accuracy);
if (enable_high_accuracy != enable_high_accuracy_) {
enable_high_accuracy_ = enable_high_accuracy;
if (geolocation_.is_bound()) {
geolocation_->SetHighAccuracyHint(enable_high_accuracy);
}
}
}
}
mojom::blink::GeolocationAccuracy Geolocation::GetAccuracyLevel() const {
if (RuntimeEnabledFeatures::ApproximateGeolocationWebVisibleAPIEnabled()) {
// When `ApproximateGeolocationWebVisibleAPI` is enabled, we use
// `AccuracyMode` to determine whether to request a precise or approximate
// permission level.
auto is_approximate = [](const auto& notifier) {
return notifier->Options()->accuracyMode().AsEnum() ==
V8AccuracyMode::Enum::kApproximate;
};
bool require_approximate =
std::ranges::any_of(*one_shots_, is_approximate) ||
std::ranges::any_of(watchers_->Notifiers(), is_approximate);
return require_approximate ? mojom::blink::GeolocationAccuracy::kApproximate
: mojom::blink::GeolocationAccuracy::kPrecise;
}
// When the feature is disabled, we always request precise location as
// approximate mode is not supported.
return mojom::blink::GeolocationAccuracy::kPrecise;
}
} // namespace blink