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

#include "extensions/browser/api/offscreen/lifetime_enforcer_factories.h"

#include <utility>

#include "base/check.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "extensions/browser/api/offscreen/audio_lifetime_enforcer.h"
#include "extensions/browser/api/offscreen/offscreen_document_lifetime_enforcer.h"
#include "extensions/common/api/offscreen.h"

namespace extensions {

namespace {

LifetimeEnforcerFactories::TestingOverride* g_testing_override = nullptr;

// A lifetime enforcer that gives offscreen documents unbounded lifetime. This
// can be used for both reasons that may always have unbounded life (like
// testing) and for reasons for which we do not have custom lifetime enforcement
// yet.
class EmptyLifetimeEnforcer : public OffscreenDocumentLifetimeEnforcer {
 public:
  EmptyLifetimeEnforcer(OffscreenDocumentHost* offscreen_document,
                        TerminationCallback termination_callback,
                        NotifyInactiveCallback notify_inactive_callback)
      : OffscreenDocumentLifetimeEnforcer(offscreen_document,
                                          std::move(termination_callback),
                                          std::move(notify_inactive_callback)) {
  }
  ~EmptyLifetimeEnforcer() override = default;

  // OffscreenDocumentLifetimeEnforcer:
  bool IsActive() override {
    // For the empty lifetime enforcer, we always assume the document is
    // active.
    return true;
  }
};

std::unique_ptr<OffscreenDocumentLifetimeEnforcer> CreateEmptyEnforcer(
    OffscreenDocumentHost* offscreen_document,
    OffscreenDocumentLifetimeEnforcer::TerminationCallback termination_callback,
    OffscreenDocumentLifetimeEnforcer::NotifyInactiveCallback
        notify_inactive_callback) {
  return std::make_unique<EmptyLifetimeEnforcer>(
      offscreen_document, std::move(termination_callback),
      std::move(notify_inactive_callback));
}

std::unique_ptr<OffscreenDocumentLifetimeEnforcer> CreateAudioLifetimeEnforcer(
    OffscreenDocumentHost* offscreen_document,
    OffscreenDocumentLifetimeEnforcer::TerminationCallback termination_callback,
    OffscreenDocumentLifetimeEnforcer::NotifyInactiveCallback
        notify_inactive_callback) {
  return std::make_unique<AudioLifetimeEnforcer>(
      offscreen_document, std::move(termination_callback),
      std::move(notify_inactive_callback));
}

LifetimeEnforcerFactories& GetFactoriesInstance() {
  static base::NoDestructor<LifetimeEnforcerFactories> instance;
  return *instance;
}

using FactoryMethodPtr = std::unique_ptr<OffscreenDocumentLifetimeEnforcer> (*)(
    OffscreenDocumentHost*,
    OffscreenDocumentLifetimeEnforcer::TerminationCallback,
    OffscreenDocumentLifetimeEnforcer::NotifyInactiveCallback);

struct ReasonAndFactoryMethodPair {
  api::offscreen::Reason reason;
  FactoryMethodPtr factory_method;
};

// A mapping between each of the different reasons and their corresponding
// factory methods.
constexpr ReasonAndFactoryMethodPair kReasonAndFactoryMethodPairs[] = {
    {api::offscreen::Reason::kTesting, &CreateEmptyEnforcer},
    {api::offscreen::Reason::kAudioPlayback, &CreateAudioLifetimeEnforcer},
    // The following reasons do not currently have bespoke lifetime enforcement.
    // This enforcement can be added on as-appropriate basis.
    {api::offscreen::Reason::kIframeScripting, &CreateEmptyEnforcer},
    {api::offscreen::Reason::kDomScraping, &CreateEmptyEnforcer},
    {api::offscreen::Reason::kBlobs, &CreateEmptyEnforcer},
    {api::offscreen::Reason::kDomParser, &CreateEmptyEnforcer},
    {api::offscreen::Reason::kUserMedia, &CreateEmptyEnforcer},
    {api::offscreen::Reason::kDisplayMedia, &CreateEmptyEnforcer},
    {api::offscreen::Reason::kWebRtc, &CreateEmptyEnforcer},
    {api::offscreen::Reason::kClipboard, &CreateEmptyEnforcer},
    {api::offscreen::Reason::kLocalStorage, &CreateEmptyEnforcer},
    {api::offscreen::Reason::kWorkers, &CreateEmptyEnforcer},
    {api::offscreen::Reason::kBatteryStatus, &CreateEmptyEnforcer},
    {api::offscreen::Reason::kMatchMedia, &CreateEmptyEnforcer},
    {api::offscreen::Reason::kGeolocation, &CreateEmptyEnforcer}};

static_assert(std::size(kReasonAndFactoryMethodPairs) ==
                  std::to_underlying(api::offscreen::Reason::kMaxValue),
              "Factory method size does not equal reason size.");

}  // namespace

LifetimeEnforcerFactories::LifetimeEnforcerFactories() {
  InitializeFactories();
}

LifetimeEnforcerFactories::~LifetimeEnforcerFactories() = default;

// static
std::unique_ptr<OffscreenDocumentLifetimeEnforcer>
LifetimeEnforcerFactories::GetLifetimeEnforcer(
    api::offscreen::Reason reason,
    OffscreenDocumentHost* offscreen_document,
    OffscreenDocumentLifetimeEnforcer::TerminationCallback termination_callback,
    OffscreenDocumentLifetimeEnforcer::NotifyInactiveCallback
        notify_inactive_callback) {
  if (g_testing_override) {
    auto iter = g_testing_override->map().find(reason);
    if (iter != g_testing_override->map().end()) {
      return iter->second.Run(offscreen_document,
                              std::move(termination_callback),
                              std::move(notify_inactive_callback));
    }
  }

  auto& factories = GetFactoriesInstance();
  auto iter = factories.map_.find(reason);
  CHECK(iter != factories.map_.end())
      << "No factory registered for: " << api::offscreen::ToString(reason);
  return iter->second.Run(offscreen_document, std::move(termination_callback),
                          std::move(notify_inactive_callback));
}

LifetimeEnforcerFactories::TestingOverride::TestingOverride() {
  DCHECK_EQ(nullptr, g_testing_override)
      << "Only a single factory override map is allowed at a time.";
  g_testing_override = this;
}

LifetimeEnforcerFactories::TestingOverride::~TestingOverride() {
  DCHECK_EQ(this, g_testing_override)
      << "Only a single factory override map is allowed at a time.";
  g_testing_override = nullptr;
}

void LifetimeEnforcerFactories::InitializeFactories() {
  for (const auto& entry : kReasonAndFactoryMethodPairs) {
    map_.emplace(entry.reason, base::BindRepeating(entry.factory_method));
  }
}

}  // namespace extensions
