chromium/ios/chrome/browser/permissions/model/geolocation_api_usage_java_script_feature.mm

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/permissions/model/geolocation_api_usage_java_script_feature.h"

#import <CoreLocation/CoreLocation.h>

#import "base/metrics/histogram_functions.h"
#import "ios/chrome/browser/geolocation/model/geolocation_manager.h"
#import "ios/web/public/js_messaging/script_message.h"

namespace {

// These values are logged to UMA. Entries should not be renumbered and
// numeric values should never be reused. Please keep in sync with
// "GeolocationAPI" in src/tools/metrics/histograms/enums.xml.
enum class GeolocationAPI {
  kClearWatch = 0,
  kGetCurrentPosition = 1,
  kWatchPosition = 2,
  kMaxValue = kWatchPosition,
};

const char kScriptName[] = "geolocation_overrides";

const char kGeolocationAPIAccessedHistogramAuthorized[] =
    "IOS.JavaScript.Permissions.Geolocation.Authorized";
const char kGeolocationAPIAccessedHistogramDenied[] =
    "IOS.JavaScript.Permissions.Geolocation.Denied";
const char kGeolocationAPIAccessedHistogramNotDetermined[] =
    "IOS.JavaScript.Permissions.Geolocation.NotDetermined";
const char kGeolocationAPIAccessedHistogramRestricted[] =
    "IOS.JavaScript.Permissions.Geolocation.Restricted";

const char kGeolocationAPIAccessedHandlerName[] =
    "GeolocationAPIAccessedHandler";

static const char kScriptMessageResponseAPINameKey[] = "api";

static const char kScriptMessageResponseAPIClearWatch[] = "clearWatch";
static const char kScriptMessageResponseAPIGetCurrentPosition[] =
    "getCurrentPosition";
static const char kScriptMessageResponseAPIWatchPosition[] = "watchPosition";

}  // namespace

// static
GeolocationAPIUsageJavaScriptFeature*
GeolocationAPIUsageJavaScriptFeature::GetInstance() {
  static base::NoDestructor<GeolocationAPIUsageJavaScriptFeature> instance;
  return instance.get();
}

// static
bool GeolocationAPIUsageJavaScriptFeature::ShouldOverrideAPI() {
  CLAuthorizationStatus status =
      [GeolocationManager sharedInstance].authorizationStatus;

  // A switch statement without a default case ensures that each enum value is
  // correctly handled, and future values will be handled correctly.
  switch (status) {
    case kCLAuthorizationStatusAuthorizedAlways:
    case kCLAuthorizationStatusAuthorizedWhenInUse:
      return false;
    case kCLAuthorizationStatusNotDetermined:
    case kCLAuthorizationStatusRestricted:
    case kCLAuthorizationStatusDenied:
      return true;
  }
}

GeolocationAPIUsageJavaScriptFeature::GeolocationAPIUsageJavaScriptFeature()
    : JavaScriptFeature(web::ContentWorld::kPageContentWorld,
                        {FeatureScript::CreateWithFilename(
                            kScriptName,
                            FeatureScript::InjectionTime::kDocumentStart,
                            FeatureScript::TargetFrames::kAllFrames,
                            FeatureScript::ReinjectionBehavior::
                                kReinjectOnDocumentRecreation)}) {}
GeolocationAPIUsageJavaScriptFeature::~GeolocationAPIUsageJavaScriptFeature() =
    default;

std::optional<std::string>
GeolocationAPIUsageJavaScriptFeature::GetScriptMessageHandlerName() const {
  return kGeolocationAPIAccessedHandlerName;
}

void GeolocationAPIUsageJavaScriptFeature::ScriptMessageReceived(
    web::WebState* web_state,
    const web::ScriptMessage& script_message) {
  const base::Value::Dict* script_dict =
      script_message.body() ? script_message.body()->GetIfDict() : nullptr;
  if (!script_dict) {
    return;
  }

  const std::string* api =
      script_dict->FindString(kScriptMessageResponseAPINameKey);
  if (!api) {
    return;
  }

  std::string metric_name;
  switch ([GeolocationManager sharedInstance].authorizationStatus) {
    case kCLAuthorizationStatusNotDetermined:
      metric_name = kGeolocationAPIAccessedHistogramNotDetermined;
      break;
    case kCLAuthorizationStatusRestricted:
      metric_name = kGeolocationAPIAccessedHistogramRestricted;
      break;
    case kCLAuthorizationStatusDenied:
      metric_name = kGeolocationAPIAccessedHistogramDenied;
      break;
    case kCLAuthorizationStatusAuthorizedAlways:
    case kCLAuthorizationStatusAuthorizedWhenInUse:
      metric_name = kGeolocationAPIAccessedHistogramAuthorized;
      break;
  }

  if (*api == kScriptMessageResponseAPIClearWatch) {
    base::UmaHistogramEnumeration(metric_name, GeolocationAPI::kClearWatch);
  } else if (*api == kScriptMessageResponseAPIGetCurrentPosition) {
    base::UmaHistogramEnumeration(metric_name,
                                  GeolocationAPI::kGetCurrentPosition);
  } else if (*api == kScriptMessageResponseAPIWatchPosition) {
    base::UmaHistogramEnumeration(metric_name, GeolocationAPI::kWatchPosition);
  }
}