910e62b5创建于 1月15日历史提交
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/geolocation/geolocation_service_impl.h"

#include <utility>

#include "base/check.h"
#include "base/functional/bind.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/features.h"
#include "content/browser/permissions/permission_controller_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/permission_descriptor_util.h"
#include "content/public/browser/permission_request_description.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "services/device/public/mojom/geoposition.mojom.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"

#if BUILDFLAG(IS_IOS)
#include "services/device/public/cpp/geolocation/geolocation_system_permission_manager.h"
#endif

namespace content {

namespace {

using GeolocationPermissionLevel = device::mojom::GeolocationPermissionLevel;

GeolocationPermissionLevel GetPermissionLevel(
    const PermissionResult& permission_result) {
  if (base::FeatureList::IsEnabled(
          content_settings::features::kApproximateGeolocationPermission)) {
    if (permission_result.status == blink::mojom::PermissionStatus::GRANTED) {
      // A GRANTED permission must have an associated setting. The setting is
      // assumed to be the `GeolocationSetting` variant, which is then
      // extracted.
      CHECK(permission_result.retrieved_permission_setting.has_value());
      GeolocationSetting geo_setting = std::get<GeolocationSetting>(
          *(permission_result.retrieved_permission_setting));
      if (geo_setting.precise == PermissionOption::kAllowed) {
        return GeolocationPermissionLevel::kPrecise;
      } else if (geo_setting.approximate == PermissionOption::kAllowed) {
        return GeolocationPermissionLevel::kApproximate;
      }
    }
    // Otherwise, the permission is considered denied.
    return GeolocationPermissionLevel::kDenied;
  }
  // With the feature disabled, the result is either granted for precise or
  // denied.
  return permission_result.status == blink::mojom::PermissionStatus::GRANTED
             ? GeolocationPermissionLevel::kPrecise
             : GeolocationPermissionLevel::kDenied;
}
}  // namespace

GeolocationServiceImplContext::GeolocationServiceImplContext() = default;

GeolocationServiceImplContext::~GeolocationServiceImplContext() = default;

void GeolocationServiceImplContext::RequestPermission(
    RenderFrameHost* render_frame_host,
    bool user_gesture,
    PermissionCallback callback) {
  if (has_pending_permission_request_) {
    mojo::ReportBadMessage(
        "GeolocationService client may only create one Geolocation at a "
        "time.");
    return;
  }

  has_pending_permission_request_ = true;

  render_frame_host->GetBrowserContext()
      ->GetPermissionController()
      ->RequestPermissionFromCurrentDocument(
          render_frame_host,
          PermissionRequestDescription(
              content::PermissionDescriptorUtil::
                  CreatePermissionDescriptorForPermissionType(
                      blink::PermissionType::GEOLOCATION),
              user_gesture),
          base::BindOnce(&GeolocationServiceImplContext::HandlePermissionResult,
                         weak_factory_.GetWeakPtr(), std::move(callback)));
}

void GeolocationServiceImplContext::HandlePermissionResult(
    PermissionCallback callback,
    PermissionResult permission_result) {
  has_pending_permission_request_ = false;
  std::move(callback).Run(permission_result);
}

GeolocationServiceImpl::GeolocationServiceImpl(
    device::mojom::GeolocationContext* geolocation_context,
    RenderFrameHost* render_frame_host)
    : geolocation_context_(geolocation_context),
      render_frame_host_(render_frame_host) {
  DCHECK(geolocation_context);
  DCHECK(render_frame_host);
}

GeolocationServiceImpl::~GeolocationServiceImpl() {
  DecrementActivityCount();
}

void GeolocationServiceImpl::Bind(
    mojo::PendingReceiver<blink::mojom::GeolocationService> receiver) {
  receiver_set_.Add(this, std::move(receiver),
                    std::make_unique<GeolocationServiceImplContext>());
  receiver_set_.set_disconnect_handler(base::BindRepeating(
      &GeolocationServiceImpl::OnDisconnected, base::Unretained(this)));
#if BUILDFLAG(IS_IOS)
  device::GeolocationSystemPermissionManager*
      geolocation_system_permission_manager =
          device::GeolocationSystemPermissionManager::GetInstance();
  if (geolocation_system_permission_manager) {
    geolocation_system_permission_manager->RequestSystemPermission();
  }
#endif
}

void GeolocationServiceImpl::CreateGeolocation(
    mojo::PendingReceiver<device::mojom::Geolocation> receiver,
    bool user_gesture,
    CreateGeolocationCallback callback) {
  if (!render_frame_host_->IsFeatureEnabled(
          network::mojom::PermissionsPolicyFeature::kGeolocation)) {
    std::move(callback).Run(blink::mojom::PermissionStatus::DENIED);
    return;
  }

  // If the geolocation service is destroyed before the callback is run, ensure
  // it is called with DENIED status.
  auto scoped_callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
      std::move(callback), blink::mojom::PermissionStatus::DENIED);

  receiver_set_.current_context()->RequestPermission(
      render_frame_host_, user_gesture,
      // The owning RenderFrameHost might be destroyed before the permission
      // request finishes. To avoid calling a callback on a destroyed object,
      // use a WeakPtr and skip the callback if the object is invalid.
      base::BindOnce(
          &GeolocationServiceImpl::CreateGeolocationWithPermissionResult,
          weak_factory_.GetWeakPtr(), std::move(receiver),
          std::move(scoped_callback)));
}

void GeolocationServiceImpl::CreateGeolocationWithPermissionResult(
    mojo::PendingReceiver<device::mojom::Geolocation> receiver,
    CreateGeolocationCallback callback,
    PermissionResult permission_result) {
  GeolocationPermissionLevel permission_level =
      GetPermissionLevel(permission_result);
  if (permission_level == GeolocationPermissionLevel::kDenied) {
    std::move(callback).Run(blink::mojom::PermissionStatus::DENIED);
    return;
  }

  std::move(callback).Run(blink::mojom::PermissionStatus::GRANTED);
  IncrementActivityCount();

  requesting_origin_ =
      render_frame_host_->GetMainFrame()->GetLastCommittedOrigin();
  auto requesting_url =
      render_frame_host_->GetMainFrame()->GetLastCommittedURL();

  bool has_precise_permission =
      permission_level == GeolocationPermissionLevel::kPrecise;
  geolocation_context_->BindGeolocation(
      std::move(receiver), requesting_url,
      device::mojom::GeolocationClientId::kGeolocationServiceImpl,
      has_precise_permission);
  subscription_id_ =
      PermissionControllerImpl::FromBrowserContext(
          render_frame_host_->GetBrowserContext())
          ->SubscribeToPermissionResultChange(
              PermissionDescriptorUtil::
                  CreatePermissionDescriptorForPermissionType(
                      blink::PermissionType::GEOLOCATION),
              /*render_process_host=*/nullptr, render_frame_host_,
              requesting_url,
              /*should_include_device_status=*/false,
              base::BindRepeating(
                  &GeolocationServiceImpl::HandlePermissionResultChange,
                  weak_factory_.GetWeakPtr()));
}

void GeolocationServiceImpl::HandlePermissionResultChange(
    PermissionResult permission_result) {
  GeolocationPermissionLevel permission_level =
      GetPermissionLevel(permission_result);
  if (permission_level == GeolocationPermissionLevel::kDenied &&
      subscription_id_.value()) {
    PermissionControllerImpl::FromBrowserContext(
        render_frame_host_->GetBrowserContext())
        ->UnsubscribeFromPermissionResultChange(subscription_id_);
    DecrementActivityCount();
  }
  geolocation_context_->OnPermissionUpdated(requesting_origin_,
                                            permission_level);
}

void GeolocationServiceImpl::OnDisconnected() {
  if (receiver_set_.empty()) {
    DecrementActivityCount();
  }
}

void GeolocationServiceImpl::IncrementActivityCount() {
  is_sending_updates_ = true;
  auto* web_contents = WebContents::FromRenderFrameHost(render_frame_host_);
  static_cast<WebContentsImpl*>(web_contents)
      ->IncrementGeolocationActiveFrameCount();
}

void GeolocationServiceImpl::DecrementActivityCount() {
  if (is_sending_updates_) {
    is_sending_updates_ = false;
    auto* web_contents = WebContents::FromRenderFrameHost(render_frame_host_);
    static_cast<WebContentsImpl*>(web_contents)
        ->DecrementGeolocationActiveFrameCount();
  }
}

}  // namespace content