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

#include "third_party/blink/renderer/modules/badging/navigator_badge.h"

#include "build/build_config.h"
#include "third_party/blink/public/platform/browser_interface_broker_proxy.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.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/settings.h"
#include "third_party/blink/renderer/core/workers/worker_navigator.h"
#include "third_party/blink/renderer/modules/notifications/notification_manager.h"
#include "third_party/blink/renderer/platform/bindings/exception_code.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"

namespace blink {

const char NavigatorBadge::kSupplementName[] = "NavigatorBadge";

// static
NavigatorBadge& NavigatorBadge::From(ScriptState* script_state) {
  DCHECK(IsAllowed(script_state));
  ExecutionContext* context = ExecutionContext::From(script_state);
  NavigatorBadge* supplement =
      Supplement<ExecutionContext>::From<NavigatorBadge>(context);
  if (!supplement) {
    supplement = MakeGarbageCollected<NavigatorBadge>(context);
    ProvideTo(*context, supplement);
  }
  return *supplement;
}

NavigatorBadge::NavigatorBadge(ExecutionContext* context)
    : Supplement(*context) {}

// static
ScriptPromise<IDLUndefined> NavigatorBadge::setAppBadge(
    ScriptState* script_state,
    Navigator& /*navigator*/,
    ExceptionState& exception_state) {
  return SetAppBadgeHelper(script_state, mojom::blink::BadgeValue::NewFlag(0),
                           exception_state);
}

// static
ScriptPromise<IDLUndefined> NavigatorBadge::setAppBadge(
    ScriptState* script_state,
    WorkerNavigator& /*navigator*/,
    ExceptionState& exception_state) {
  return SetAppBadgeHelper(script_state, mojom::blink::BadgeValue::NewFlag(0),
                           exception_state);
}

// static
ScriptPromise<IDLUndefined> NavigatorBadge::setAppBadge(
    ScriptState* script_state,
    Navigator& /*navigator*/,
    uint64_t content,
    ExceptionState& exception_state) {
  return SetAppBadgeHelper(script_state,
                           mojom::blink::BadgeValue::NewNumber(content),
                           exception_state);
}

// static
ScriptPromise<IDLUndefined> NavigatorBadge::setAppBadge(
    ScriptState* script_state,
    WorkerNavigator& /*navigator*/,
    uint64_t content,
    ExceptionState& exception_state) {
  return SetAppBadgeHelper(script_state,
                           mojom::blink::BadgeValue::NewNumber(content),
                           exception_state);
}

// static
ScriptPromise<IDLUndefined> NavigatorBadge::clearAppBadge(
    ScriptState* script_state,
    Navigator& /*navigator*/,
    ExceptionState& exception_state) {
  return ClearAppBadgeHelper(script_state, exception_state);
}

// static
ScriptPromise<IDLUndefined> NavigatorBadge::clearAppBadge(
    ScriptState* script_state,
    WorkerNavigator& /*navigator*/,
    ExceptionState& exception_state) {
  return ClearAppBadgeHelper(script_state, exception_state);
}

void NavigatorBadge::Trace(Visitor* visitor) const {
  Supplement<ExecutionContext>::Trace(visitor);
}

// static
ScriptPromise<IDLUndefined> NavigatorBadge::SetAppBadgeHelper(
    ScriptState* script_state,
    mojom::blink::BadgeValuePtr badge_value,
    ExceptionState& exception_state) {
  if (badge_value->is_number() && badge_value->get_number() == 0)
    return ClearAppBadgeHelper(script_state, exception_state);

  if (!IsAllowed(script_state)) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kNotAllowedError,
        "The badge API is not allowed in this context");
    return EmptyPromise();
  }

#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA)
  // TODO(crbug.com/1413916): The service is implemented in Chrome, so it may
  // not be provided in other embedders. Ensure that case is handled properly.
  From(script_state).badge_service()->SetBadge(std::move(badge_value));
#endif

  ExecutionContext* context = ExecutionContext::From(script_state);
  if (context) {
    mojom::blink::WebFeature feature =
        context->IsWindow()
            ? mojom::blink::WebFeature::
                  kBadgeSetWithoutNotificationPermissionInBrowserWindow
            : mojom::blink::WebFeature::
                  kBadgeSetWithoutNotificationPermissionInWorker;
    if (auto* window = DynamicTo<LocalDOMWindow>(context)) {
      LocalFrame* frame = window->GetFrame();
      if (frame && frame->GetSettings() &&
          !frame->GetSettings()->GetWebAppScope().empty()) {
        feature = mojom::blink::WebFeature::
            kBadgeSetWithoutNotificationPermissionInAppWindow;
      }
    }
    NotificationManager::From(context)->GetPermissionStatusAsync(BindOnce(
        [](mojom::blink::WebFeature feature, UseCounter* counter,
           mojom::blink::PermissionStatus status) {
          if (status != mojom::blink::PermissionStatus::GRANTED) {
            UseCounter::Count(counter, feature);
          }
        },
        feature, WrapWeakPersistent(context)));
  }
  return ToResolvedUndefinedPromise(script_state);
}

// static
ScriptPromise<IDLUndefined> NavigatorBadge::ClearAppBadgeHelper(
    ScriptState* script_state,
    ExceptionState& exception_state) {
  if (!IsAllowed(script_state)) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kNotAllowedError,
        "The badge API is not allowed in this context");
    return EmptyPromise();
  }

#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA)
  // TODO(crbug.com/1413916): The service is implemented in Chrome, so it may
  // not be provided in other embedders. Ensure that case is handled properly.
  From(script_state).badge_service()->ClearBadge();
#endif
  return ToResolvedUndefinedPromise(script_state);
}

// static
bool NavigatorBadge::IsAllowed(ScriptState* script_state) {
  ExecutionContext* context = ExecutionContext::From(script_state);
  return !context->IsInFencedFrame();
}

mojo::Remote<mojom::blink::BadgeService> NavigatorBadge::badge_service() {
  mojo::Remote<mojom::blink::BadgeService> badge_service;
  GetSupplementable()->GetBrowserInterfaceBroker().GetInterface(
      badge_service.BindNewPipeAndPassReceiver());
  DCHECK(badge_service);

  return badge_service;
}

}  // namespace blink
