// 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/loader/reconnectable_url_loader_factory.h"

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "content/browser/loader/url_loader_factory_utils.h"
#include "services/network/public/cpp/cross_thread_pending_shared_url_loader_factory.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"

namespace content {

ReconnectableURLLoaderFactory::ReconnectableURLLoaderFactory(
    CreateCallback create_url_loader_factory_callback)
    : create_url_loader_factory_callback_(
          std::move(create_url_loader_factory_callback)) {}

ReconnectableURLLoaderFactory::~ReconnectableURLLoaderFactory() = default;

void ReconnectableURLLoaderFactory::CreateLoaderAndStart(
    mojo::PendingReceiver<network::mojom::URLLoader> receiver,
    int32_t request_id,
    uint32_t options,
    const network::ResourceRequest& url_request,
    mojo::PendingRemote<network::mojom::URLLoaderClient> client,
    const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
  if (network::mojom::URLLoaderFactory* factory = GetURLLoaderFactory()) {
    factory->CreateLoaderAndStart(std::move(receiver), request_id, options,
                                  url_request, std::move(client),
                                  traffic_annotation);
  }
}

void ReconnectableURLLoaderFactory::Clone(
    mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver) {
  if (network::mojom::URLLoaderFactory* factory = GetURLLoaderFactory()) {
    factory->Clone(std::move(receiver));
  }
}

std::unique_ptr<network::PendingSharedURLLoaderFactory>
ReconnectableURLLoaderFactory::Clone() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return std::make_unique<network::CrossThreadPendingSharedURLLoaderFactory>(
      this);
}

network::mojom::URLLoaderFactory*
ReconnectableURLLoaderFactory::GetURLLoaderFactory() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Create the URLLoaderFactory as needed, but make sure not to reuse a
  // previously created one if the test override has changed.
  if (url_loader_factory_ && url_loader_factory_.is_connected() &&
      is_test_url_loader_factory_ ==
          !!url_loader_factory::GetTestingInterceptor()) {
    return url_loader_factory_.get();
  }

  is_test_url_loader_factory_ = !!url_loader_factory::GetTestingInterceptor();
  url_loader_factory_.reset();

  mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory;
  create_url_loader_factory_callback_.Run(&url_loader_factory);
  if (!url_loader_factory) {
    return nullptr;
  }

  url_loader_factory_.Bind(std::move(url_loader_factory));
  return url_loader_factory_.get();
}

void ReconnectableURLLoaderFactory::Reset() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  url_loader_factory_.reset();
}

void ReconnectableURLLoaderFactory::FlushForTesting() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (url_loader_factory_) {
    url_loader_factory_.FlushForTesting();  // IN-TEST
  }
}

// -----------------------------------------------------------------------------

class ReconnectableURLLoaderFactoryForIOThread::
    PendingURLLoaderFactoryForIOThread final
    : public network::PendingSharedURLLoaderFactory {
 public:
  explicit PendingURLLoaderFactoryForIOThread(
      scoped_refptr<ReconnectableURLLoaderFactoryForIOThread> factory_getter)
      : factory_getter_(std::move(factory_getter)) {
    CHECK(factory_getter_);
  }

  PendingURLLoaderFactoryForIOThread(
      const PendingURLLoaderFactoryForIOThread&) = delete;
  PendingURLLoaderFactoryForIOThread& operator=(
      const PendingURLLoaderFactoryForIOThread&) = delete;

  ~PendingURLLoaderFactoryForIOThread() override = default;

 protected:
  // PendingSharedURLLoaderFactory implementation.
  scoped_refptr<network::SharedURLLoaderFactory> CreateFactory() override;

  const scoped_refptr<ReconnectableURLLoaderFactoryForIOThread> factory_getter_;
};

class ReconnectableURLLoaderFactoryForIOThread::URLLoaderFactoryForIOThread
    final : public network::SharedURLLoaderFactory {
 public:
  explicit URLLoaderFactoryForIOThread(
      scoped_refptr<ReconnectableURLLoaderFactoryForIOThread> factory_getter)
      : factory_getter_(std::move(factory_getter)) {
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    CHECK(factory_getter_);
  }

  URLLoaderFactoryForIOThread(const URLLoaderFactoryForIOThread&) = delete;
  URLLoaderFactoryForIOThread& operator=(const URLLoaderFactoryForIOThread&) =
      delete;

  // mojom::URLLoaderFactory implementation:
  void CreateLoaderAndStart(
      mojo::PendingReceiver<network::mojom::URLLoader> receiver,
      int32_t request_id,
      uint32_t options,
      const network::ResourceRequest& url_request,
      mojo::PendingRemote<network::mojom::URLLoaderClient> client,
      const net::MutableNetworkTrafficAnnotationTag& traffic_annotation)
      override {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    factory_getter_->GetURLLoaderFactory()->CreateLoaderAndStart(
        std::move(receiver), request_id, options, url_request,
        std::move(client), traffic_annotation);
  }

  void Clone(mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver)
      override {
    factory_getter_->GetURLLoaderFactory()->Clone(std::move(receiver));
  }

  // SharedURLLoaderFactory implementation:
  std::unique_ptr<network::PendingSharedURLLoaderFactory> Clone() override {
    NOTREACHED() << "This isn't supported. If you need a SharedURLLoaderFactory"
                    " on the UI thread, get it from StoragePartition.";
  }

 private:
  friend class base::RefCounted<URLLoaderFactoryForIOThread>;
  ~URLLoaderFactoryForIOThread() override = default;

  const scoped_refptr<ReconnectableURLLoaderFactoryForIOThread> factory_getter_;
};

scoped_refptr<network::SharedURLLoaderFactory>
ReconnectableURLLoaderFactoryForIOThread::PendingURLLoaderFactoryForIOThread::
    CreateFactory() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  return base::MakeRefCounted<URLLoaderFactoryForIOThread>(
      std::move(factory_getter_));
}

// -----------------------------------------------------------------------------

ReconnectableURLLoaderFactoryForIOThread::
    ReconnectableURLLoaderFactoryForIOThread(
        CreateCallback create_url_loader_factory_callback)
    : create_url_loader_factory_callback_(
          std::move(create_url_loader_factory_callback)) {}

void ReconnectableURLLoaderFactoryForIOThread::Initialize() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // Create a mojo::PendingRemote<URLLoaderFactory> synchronously and push it to
  // the IO thread. If the pipe errors out later due to a network service crash,
  // the pipe is created on the IO thread, and the request send back to the UI
  // thread.
  // TODO(mmenke):  Is one less thread hop on startup worth the extra complexity
  // of two different pipe creation paths?
  mojo::PendingRemote<network::mojom::URLLoaderFactory> network_factory;
  HandleNetworkFactoryRequestOnUIThread(
      network_factory.InitWithNewPipeAndPassReceiver());

  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          &ReconnectableURLLoaderFactoryForIOThread::InitializeOnIOThread, this,
          std::move(network_factory)));
}

std::unique_ptr<network::PendingSharedURLLoaderFactory>
ReconnectableURLLoaderFactoryForIOThread::CloneForIOThread() {
  return std::make_unique<PendingURLLoaderFactoryForIOThread>(
      base::WrapRefCounted(this));
}

network::mojom::URLLoaderFactory*
ReconnectableURLLoaderFactoryForIOThread::GetURLLoaderFactory() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  if (url_loader_factory_ && url_loader_factory_.is_connected() &&
      is_test_url_loader_factory_ ==
          url_loader_factory::HasInterceptorOnIOThreadForTesting()) {
    return url_loader_factory_.get();
  }

  is_test_url_loader_factory_ =
      url_loader_factory::HasInterceptorOnIOThreadForTesting();
  mojo::Remote<network::mojom::URLLoaderFactory> network_factory;
  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&ReconnectableURLLoaderFactoryForIOThread::
                         HandleNetworkFactoryRequestOnUIThread,
                     this, network_factory.BindNewPipeAndPassReceiver()));
  ReinitializeOnIOThread(std::move(network_factory));
  return url_loader_factory_.get();
}

void ReconnectableURLLoaderFactoryForIOThread::Reset() {
  Initialize();
}

void ReconnectableURLLoaderFactoryForIOThread::FlushForTesting() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  base::RunLoop run_loop;
  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          &ReconnectableURLLoaderFactoryForIOThread::FlushOnIOThreadForTesting,
          this, run_loop.QuitClosure()));
  run_loop.Run();
}

void ReconnectableURLLoaderFactoryForIOThread::FlushOnIOThreadForTesting(
    base::OnceClosure callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (url_loader_factory_) {
    url_loader_factory_.FlushAsyncForTesting(std::move(callback));  // IN-TEST
  }
}

ReconnectableURLLoaderFactoryForIOThread::
    ~ReconnectableURLLoaderFactoryForIOThread() = default;

void ReconnectableURLLoaderFactoryForIOThread::InitializeOnIOThread(
    mojo::PendingRemote<network::mojom::URLLoaderFactory> network_factory) {
  ReinitializeOnIOThread(mojo::Remote<network::mojom::URLLoaderFactory>(
      std::move(network_factory)));
}

void ReconnectableURLLoaderFactoryForIOThread::ReinitializeOnIOThread(
    mojo::Remote<network::mojom::URLLoaderFactory> network_factory) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DCHECK(network_factory.is_bound());
  // Set a disconnect handler so that connection errors on the pipes are
  // noticed, but the class doesn't actually do anything when the error is
  // observed - instead, a new pipe is created in GetURLLoaderFactory() as
  // needed. This is to avoid incrementing the reference count of |this| in the
  // callback, as that could result in increasing the reference count from 0 to
  // 1 while there's a pending task to delete |this|. See
  // https://crbug.com/870942 for more details.
  network_factory.set_disconnect_handler(base::DoNothing());
  url_loader_factory_ = std::move(network_factory);
}

void ReconnectableURLLoaderFactoryForIOThread::
    HandleNetworkFactoryRequestOnUIThread(
        mojo::PendingReceiver<network::mojom::URLLoaderFactory>
            network_factory_receiver) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  mojo::PendingRemote<network::mojom::URLLoaderFactory> factory_remote;
  create_url_loader_factory_callback_.Run(&factory_remote);

  if (!factory_remote) {
    // The underlying URLLoaderFactory has went away while
    // `ReconnectableURLLoaderFactoryForIOThread` is still held by consumers.
    return;
  }

  CHECK(mojo::FusePipes(std::move(network_factory_receiver),
                        std::move(factory_remote)));
}

// -----------------------------------------------------------------------------

ReconnectableURLLoaderFactoryForIOThreadWrapper::
    ReconnectableURLLoaderFactoryForIOThreadWrapper(
        CreateCallback create_url_loader_factory_callback)
    : factory_(base::MakeRefCounted<ReconnectableURLLoaderFactory>(
          create_url_loader_factory_callback)),
      factory_for_io_thread_(
          base::MakeRefCounted<ReconnectableURLLoaderFactoryForIOThread>(
              create_url_loader_factory_callback)) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
}

ReconnectableURLLoaderFactoryForIOThreadWrapper::
    ~ReconnectableURLLoaderFactoryForIOThreadWrapper() = default;

}  // namespace content
