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

#include "android_webview/browser/cookie_manager.h"

#include <stdint.h>

#include <memory>
#include <optional>
#include <utility>
#include <vector>

#include "android_webview/browser/aw_browser_context.h"
#include "android_webview/browser/aw_browser_context_store.h"
#include "android_webview/browser/aw_client_hints_controller_delegate.h"
#include "android_webview/browser/aw_cookie_access_policy.h"
#include "android_webview/common/aw_features.h"
#include "android_webview/common/aw_switches.h"
#include "base/android/android_info.h"
#include "base/android/apk_info.h"
#include "base/android/callback_android.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/command_line.h"
#include "base/containers/circular_deque.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/cookie_store_factory.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_constants.h"
#include "net/cookies/cookie_monster.h"
#include "net/cookies/cookie_options.h"
#include "net/cookies/cookie_partition_key.h"
#include "net/cookies/cookie_store.h"
#include "net/cookies/cookie_util.h"
#include "net/cookies/parsed_cookie.h"
#include "net/url_request/url_request_context.h"
#include "services/network/cookie_access_delegate_impl.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
#include "third_party/jni_zero/default_conversions.h"
#include "url/url_constants.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "android_webview/browser_jni_headers/AwCookieManager_jni.h"

using base::WaitableEvent;
using content::BrowserThread;
using net::CookieList;

// In the future, we may instead want to inject an explicit CookieStore
// dependency into this object during process initialization to avoid
// depending on the URLRequestContext.
// See issue http://crbug.com/157683

// On the CookieManager methods without a callback and methods with a callback
// when that callback is null can be called from any thread, including threads
// without a message loop. Methods with a non-null callback must be called on
// a thread with a running message loop.

namespace android_webview {

namespace {

void MaybeRunCookieCallback(base::OnceCallback<void(bool)> callback,
                            const bool& result) {
  if (callback)
    std::move(callback).Run(result);
}

const char kSecureCookieHistogramName[] = "Android.WebView.SecureCookieAction";

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class SecureCookieAction {
  kInvalidUrl = 0,
  kAlreadySecureScheme = 1,
  kInvalidCookie = 2,
  kNotASecureCookie = 3,
  kFixedUp = 4,
  kDisallowedAndroidR = 5,
  kMaxValue = kDisallowedAndroidR,
};

GURL MaybeFixUpSchemeForSecureCookie(const GURL& host,
                                     const std::string& value,
                                     bool workaround_http_secure_cookies,
                                     bool* should_allow_cookie) {
  net::ParsedCookie parsed_cookie(value);

  *should_allow_cookie = true;

  // Log message for catching strict secure cookies related bugs.
  // TODO(ntfschr): try to remove this, based on UMA stats
  // (https://crbug.com/933981)
  if (!host.is_valid()) {
    base::UmaHistogramEnumeration(kSecureCookieHistogramName,
                                  SecureCookieAction::kInvalidUrl);
    return host;
  }
  if (host.has_scheme() && !host.SchemeIs(url::kHttpScheme)) {
    base::UmaHistogramEnumeration(kSecureCookieHistogramName,
                                  SecureCookieAction::kAlreadySecureScheme);
    return host;
  }
  if (!parsed_cookie.IsValid()) {
    base::UmaHistogramEnumeration(kSecureCookieHistogramName,
                                  SecureCookieAction::kInvalidCookie);
    return host;
  }
  if (!parsed_cookie.IsSecure()) {
    base::UmaHistogramEnumeration(kSecureCookieHistogramName,
                                  SecureCookieAction::kNotASecureCookie);
    return host;
  }

  LOG(ERROR) << "Strict Secure Cookie policy does not allow setting a "
                "secure cookie for "
             << host.spec()
             << " for apps targeting >= R. Please either use the 'https:' "
                "scheme for this URL or omit the 'Secure' directive in the "
                "cookie value.";
  if (!workaround_http_secure_cookies) {
    // Don't allow setting this cookie if we target >= R.
    *should_allow_cookie = false;
    base::UmaHistogramEnumeration(kSecureCookieHistogramName,
                                  SecureCookieAction::kDisallowedAndroidR);
    return host;
  }

  base::UmaHistogramEnumeration(kSecureCookieHistogramName,
                                SecureCookieAction::kFixedUp);
  GURL::Replacements replace_host;
  replace_host.SetSchemeStr(url::kHttpsScheme);
  return host.ReplaceComponents(replace_host);
}

// Construct a closure which signals a waitable event if and when the closure
// is called the waitable event must still exist.
static base::OnceClosure SignalEventClosure(WaitableEvent* completion) {
  return base::BindOnce(&WaitableEvent::Signal, base::Unretained(completion));
}

static void DiscardBool(base::OnceClosure f, bool b) {
  std::move(f).Run();
}

static base::OnceCallback<void(bool)> BoolCallbackAdapter(base::OnceClosure f) {
  return base::BindOnce(&DiscardBool, std::move(f));
}

static void DiscardInt(base::OnceClosure f, int i) {
  std::move(f).Run();
}

static base::OnceCallback<void(int)> IntCallbackAdapter(base::OnceClosure f) {
  return base::BindOnce(&DiscardInt, std::move(f));
}

// Are cookies allowed for file:// URLs by default?
const bool kDefaultFileSchemeAllowed = false;

}  // namespace

// static
CookieManager* CookieManager::GetDefaultInstance() {
  static base::NoDestructor<CookieManager> instance(nullptr);
  return instance.get();
}


CookieManager::CookieManager(AwBrowserContext* const parent_context)
    : parent_context_(parent_context),
      allow_file_scheme_cookies_(kDefaultFileSchemeAllowed),
      cookie_store_created_(false),
      workaround_http_secure_cookies_(
          base::android::apk_info::target_sdk_version() <
          base::android::android_info::SDK_VERSION_R),
      cookie_store_client_thread_("CookieMonsterClient"),
      cookie_store_backend_thread_("CookieMonsterBackend"),
      setting_new_mojo_cookie_manager_(false) {
  // Apps can specify a list of Profiles (BrowserContexts) to be initialized at
  // startup, meaning this can be called after thread restrictions are applied.
  base::ScopedAllowBlocking scoped_allow_blocking;
  cookie_store_client_thread_.Start();
  cookie_store_backend_thread_.Start();
  cookie_store_task_runner_ = cookie_store_client_thread_.task_runner();
  cookie_store_path_ = GetContextPath().Append(FILE_PATH_LITERAL("Cookies"));
}

CookieManager::~CookieManager() = default;


// Executes the |task| on |cookie_store_task_runner_| and waits for it to
// complete before returning.
//
// To execute a CookieTask synchronously you must arrange for Signal to be
// called on the waitable event at some point. You can call the bool or int
// versions of ExecCookieTaskSync, these will supply the caller with a
// placeholder callback which takes an int/bool, throws it away and calls
// Signal. Alternatively you can call the version which supplies a Closure in
// which case you must call Run on it when you want the unblock the calling
// code.
//
// Ignore a bool callback.
void CookieManager::ExecCookieTaskSync(
    base::OnceCallback<void(base::OnceCallback<void(bool)>)> task) {
  WaitableEvent completion(base::WaitableEvent::ResetPolicy::AUTOMATIC,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);
  ExecCookieTask(base::BindOnce(
      std::move(task), BoolCallbackAdapter(SignalEventClosure(&completion))));

  // Waiting is necessary when implementing synchronous APIs for the WebView
  // embedder.
  base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope wait;
  completion.Wait();
}

// Ignore an int callback.
void CookieManager::ExecCookieTaskSync(
    base::OnceCallback<void(base::OnceCallback<void(int)>)> task) {
  WaitableEvent completion(base::WaitableEvent::ResetPolicy::AUTOMATIC,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);
  ExecCookieTask(base::BindOnce(
      std::move(task), IntCallbackAdapter(SignalEventClosure(&completion))));
  base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope wait;
  completion.Wait();
}

// Call the supplied closure when you want to signal that the blocked code can
// continue.
void CookieManager::ExecCookieTaskSync(
    base::OnceCallback<void(base::OnceClosure)> task) {
  WaitableEvent completion(base::WaitableEvent::ResetPolicy::AUTOMATIC,
                           base::WaitableEvent::InitialState::NOT_SIGNALED);
  ExecCookieTask(
      base::BindOnce(std::move(task), SignalEventClosure(&completion)));
  base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope wait;
  completion.Wait();
}

// Executes the |task| using |cookie_store_task_runner_|.
void CookieManager::ExecCookieTask(base::OnceClosure task) {
  base::AutoLock lock(task_queue_lock_);
  tasks_.push_back(std::move(task));
  // Unretained is safe, since android_webview::CookieManager is a singleton we
  // never destroy (we don't need PostTask to do any memory management).
  cookie_store_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(&CookieManager::RunPendingCookieTasks,
                                base::Unretained(this)));
}

void CookieManager::RunPendingCookieTasks() {
  DCHECK(cookie_store_task_runner_->RunsTasksInCurrentSequence());
  // Don't do any cookie tasks if in the middle of setting a mojo CookieManager,
  // we'll call this method when that operation is finished.
  if (setting_new_mojo_cookie_manager_)
    return;

  // Copy tasks into temp_queue to minimize the amount of time in the critical
  // section, and to mitigate live-lock issues if these tasks append to the task
  // queue themselves.
  base::circular_deque<base::OnceClosure> temp_queue;
  {
    base::AutoLock lock(task_queue_lock_);
    temp_queue.swap(tasks_);
  }
  while (!temp_queue.empty()) {
    std::move(temp_queue.front()).Run();
    temp_queue.pop_front();
  }
}

base::FilePath CookieManager::GetCookieStorePath() {
  return cookie_store_path_;
}

net::CookieStore* CookieManager::GetCookieStore() {
  // This should only be called for the default context.
  CHECK(!parent_context_);
  DCHECK(cookie_store_task_runner_->RunsTasksInCurrentSequence());

  if (!cookie_store_) {
    content::CookieStoreConfig cookie_config(
        cookie_store_path_, /* restore_old_session_cookies= */ true,
        /* persist_session_cookies= */ true);
    cookie_config.client_task_runner = cookie_store_task_runner_;
    cookie_config.background_task_runner =
        cookie_store_backend_thread_.task_runner();

    {
      base::AutoLock lock(allow_file_scheme_cookies_lock_);

      // There are some unknowns about how to correctly handle file:// cookies,
      // and our implementation for this is not robust.  http://crbug.com/582985
      //
      // TODO(mmenke): This call should be removed once we can deprecate and
      // remove the Android WebView 'CookieManager::SetAllowFileSchemeCookies'
      // method. Until then, note that this is just not a great idea.
      cookie_config.cookieable_schemes =
          net::CookieMonster::GetDefaultCookieableSchemes();
      if (allow_file_scheme_cookies_) {
        cookie_config.cookieable_schemes.emplace_back(url::kFileScheme);
      }
      cookie_store_created_ = true;
    }

    cookie_store_ =
        content::CreateCookieStore(std::move(cookie_config), nullptr);
    auto cookie_access_delegate_type =
        base::CommandLine::ForCurrentProcess()->HasSwitch(
            switches::kWebViewEnableModernCookieSameSite)
            ? network::mojom::CookieAccessDelegateType::ALWAYS_NONLEGACY
            : network::mojom::CookieAccessDelegateType::ALWAYS_LEGACY;
    cookie_store_->SetCookieAccessDelegate(
        std::make_unique<network::CookieAccessDelegateImpl>(
            cookie_access_delegate_type, nullptr /* first_party_sets */));
  }

  return cookie_store_.get();
}

network::mojom::CookieManager* CookieManager::GetMojoCookieManager() {
  DCHECK(cookie_store_task_runner_->RunsTasksInCurrentSequence());
  if (!mojo_cookie_manager_.is_bound())
    return nullptr;
  return mojo_cookie_manager_.get();
}

void CookieManager::SetMojoCookieManager(
    mojo::PendingRemote<network::mojom::CookieManager> cookie_manager_remote) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  ExecCookieTaskSync(
      base::BindOnce(&CookieManager::SetMojoCookieManagerOnCookieThread,
                     base::Unretained(this), std::move(cookie_manager_remote)));
}

void CookieManager::SetMojoCookieManagerOnCookieThread(
    mojo::PendingRemote<network::mojom::CookieManager> cookie_manager_remote,
    base::OnceClosure complete) {
  DCHECK(cookie_store_task_runner_->RunsTasksInCurrentSequence());
  setting_new_mojo_cookie_manager_ = true;
  // For simplicity, only permit this method to be called once (otherwise, we
  // must sometimes flush the mojo_cookie_manager_ instead of cookie_store_).
  DCHECK(!mojo_cookie_manager_.is_bound());
  if (!cookie_store_created_) {
    SwapMojoCookieManagerOnCookieThread(std::move(cookie_manager_remote),
                                        std::move(complete));
    return;
  }

  LOG(WARNING) << "Transferring cookies from provisional CookieManager to "
                  "network service. For issues with the provisional "
                  "CookieManager, see crbug.com/478873476.";
  GetCookieStore()->FlushStore(
      base::BindOnce(&CookieManager::SwapMojoCookieManagerOnCookieThread,
                     base::Unretained(this), std::move(cookie_manager_remote),
                     std::move(complete)));
}

void CookieManager::SwapMojoCookieManagerOnCookieThread(
    mojo::PendingRemote<network::mojom::CookieManager> cookie_manager_remote,
    base::OnceClosure complete) {
  DCHECK(cookie_store_task_runner_->RunsTasksInCurrentSequence());
  mojo_cookie_manager_.Bind(std::move(cookie_manager_remote));
  setting_new_mojo_cookie_manager_ = false;
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&CookieManager::ClearClientHintsCachedPerOriginMapIfNeeded,
                     base::Unretained(this)));
  std::move(complete).Run();  // unblock content initialization
  RunPendingCookieTasks();
}

void CookieManager::SetMojoCookieManagerNonBlocking(
    mojo::PendingRemote<network::mojom::CookieManager> cookie_manager_remote,
    mojo::PendingRemote<network::mojom::CookieStoreReadyCallback>
        ready_callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  // Non-blocking version - post task instead of ExecCookieTaskSync
  ExecCookieTask(base::BindOnce(
      &CookieManager::SetMojoCookieManagerNonBlockingOnCookieThread,
      base::Unretained(this), std::move(cookie_manager_remote),
      std::move(ready_callback)));
}

void CookieManager::SetMojoCookieManagerNonBlockingOnCookieThread(
    mojo::PendingRemote<network::mojom::CookieManager> cookie_manager_remote,
    mojo::PendingRemote<network::mojom::CookieStoreReadyCallback>
        ready_callback) {
  DCHECK(cookie_store_task_runner_->RunsTasksInCurrentSequence());
  setting_new_mojo_cookie_manager_ = true;
  DCHECK(!mojo_cookie_manager_.is_bound());

  if (!cookie_store_created_) {
    // No provisional store was created, can signal ready immediately.
    OnProvisionalStoreClosed(std::move(cookie_manager_remote),
                             std::move(ready_callback));
    return;
  }

  // Flush the provisional store, then close it and signal ready.
  GetCookieStore()->FlushStore(
      base::BindOnce(&CookieManager::CloseProvisionalStoreAndSignalReady,
                     base::Unretained(this), std::move(cookie_manager_remote),
                     std::move(ready_callback)));
}

void CookieManager::CloseProvisionalStoreAndSignalReady(
    mojo::PendingRemote<network::mojom::CookieManager> cookie_manager_remote,
    mojo::PendingRemote<network::mojom::CookieStoreReadyCallback>
        ready_callback) {
  DCHECK(cookie_store_task_runner_->RunsTasksInCurrentSequence());

  if (pending_provisional_store_operations_ > 0) {
    // There are pending operations. Save the remotes and wait for them to
    // complete. The last operation to complete will call
    // DoCloseProvisionalStoreAndSignalReady.
    waiting_to_close_provisional_store_ = true;
    deferred_cookie_manager_remote_ = std::move(cookie_manager_remote);
    deferred_ready_callback_ = std::move(ready_callback);
    return;
  }

  // No pending operations, close immediately.
  DoCloseProvisionalStoreAndSignalReady(std::move(cookie_manager_remote),
                                        std::move(ready_callback));
}

void CookieManager::DoCloseProvisionalStoreAndSignalReady(
    mojo::PendingRemote<network::mojom::CookieManager> cookie_manager_remote,
    mojo::PendingRemote<network::mojom::CookieStoreReadyCallback>
        ready_callback) {
  DCHECK(cookie_store_task_runner_->RunsTasksInCurrentSequence());
  DCHECK_EQ(pending_provisional_store_operations_, 0);

  // Close the provisional cookie store. This triggers the destructor which
  // posts backend_->Close() to the background thread.
  cookie_store_.reset();
  cookie_store_created_ = false;

  // Continue to OnProvisionalStoreClosed, which will stop the backend thread.
  OnProvisionalStoreClosed(std::move(cookie_manager_remote),
                           std::move(ready_callback));
}

void CookieManager::OnProvisionalStoreClosed(
    mojo::PendingRemote<network::mojom::CookieManager> cookie_manager_remote,
    mojo::PendingRemote<network::mojom::CookieStoreReadyCallback>
        ready_callback) {
  DCHECK(cookie_store_task_runner_->RunsTasksInCurrentSequence());

  // Synchronously wait for all pending backend tasks to complete,
  // including the backend_->Close() posted by ~CookieMonster.
  base::WaitableEvent backend_drained;
  cookie_store_backend_thread_.task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&base::WaitableEvent::Signal,
                                base::Unretained(&backend_drained)));
  backend_drained.Wait();

  // Bind the new mojo cookie manager.
  mojo_cookie_manager_.Bind(std::move(cookie_manager_remote));

  // Signal that the provisional store is closed and it's safe to open the
  // cookie database in the Network Service.
  mojo::Remote<network::mojom::CookieStoreReadyCallback> callback_remote(
      std::move(ready_callback));
  callback_remote->OnCookieStoreReady();

  setting_new_mojo_cookie_manager_ = false;
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&CookieManager::ClearClientHintsCachedPerOriginMapIfNeeded,
                     base::Unretained(this)));
  RunPendingCookieTasks();
}

void CookieManager::OnProvisionalStoreOperationComplete() {
  DCHECK(cookie_store_task_runner_->RunsTasksInCurrentSequence());
  DCHECK_GT(pending_provisional_store_operations_, 0);
  pending_provisional_store_operations_--;

  // If we're waiting to close the provisional store and this was the last
  // pending operation, proceed with closing. Post as a separate task to avoid
  // destroying the CookieMonster from within its own InvokeQueue() method.
  // This callback can be invoked from within CookieMonster::InvokeQueue()
  // (when a queued cookie operation completes and fires its callback chain).
  // Calling DoCloseProvisionalStoreAndSignalReady synchronously would call
  // cookie_store_.reset(), destroying the CookieMonster while InvokeQueue()
  // is still on the call stack -- a use-after-free.
  if (waiting_to_close_provisional_store_ &&
      pending_provisional_store_operations_ == 0) {
    waiting_to_close_provisional_store_ = false;
    cookie_store_task_runner_->PostTask(
        FROM_HERE,
        base::BindOnce(&CookieManager::DoCloseProvisionalStoreAndSignalReady,
                       base::Unretained(this),
                       std::move(deferred_cookie_manager_remote_),
                       std::move(deferred_ready_callback_)));
  }
}

base::android::ScopedJavaLocalRef<jobject>
CookieManager::GetJavaCookieManager() {
  if (!java_obj_) {
    JNIEnv* env = base::android::AttachCurrentThread();
    java_obj_ =
        Java_AwCookieManager_create(env, reinterpret_cast<intptr_t>(this));
  }
  return base::android::ScopedJavaLocalRef<jobject>(java_obj_);
}

void CookieManager::SetWorkaroundHttpSecureCookiesForTesting(JNIEnv* env,
                                                             bool allow) {
  ExecCookieTaskSync(
      base::BindOnce(&CookieManager::SetWorkaroundHttpSecureCookiesAsyncHelper,
                     base::Unretained(this), allow));
}

void CookieManager::SetWorkaroundHttpSecureCookiesAsyncHelper(
    bool allow,
    base::OnceClosure complete) {
  DCHECK(cookie_store_task_runner_->RunsTasksInCurrentSequence());
  workaround_http_secure_cookies_ = allow;
  std::move(complete).Run();
}

void CookieManager::SetShouldAcceptCookies(JNIEnv* env, bool accept) {
  cookie_access_policy_.SetShouldAcceptCookies(accept);
}

bool CookieManager::GetShouldAcceptCookies(JNIEnv* env) {
  return cookie_access_policy_.GetShouldAcceptCookies();
}

void CookieManager::SetCookie(const std::string& url,
                              const std::string& cookie_value,
                              base::OnceCallback<void(bool)> callback) {
  GURL host(url);

  ExecCookieTask(base::BindOnce(&CookieManager::SetCookieHelper,
                                base::Unretained(this), host, cookie_value,
                                std::move(callback)));
}

void CookieManager::SetCookieSync(const std::string& url,
                                  const std::string& value) {
  GURL host(url);
  std::string cookie_value(value);

  ExecCookieTaskSync(base::BindOnce(&CookieManager::SetCookieHelper,
                                    base::Unretained(this), host,
                                    cookie_value));
}

void CookieManager::SetCookieHelper(const GURL& host,
                                    const std::string& value,
                                    base::OnceCallback<void(bool)> callback) {
  DCHECK(cookie_store_task_runner_->RunsTasksInCurrentSequence());

  bool should_allow_cookie = true;
  const GURL& new_host = MaybeFixUpSchemeForSecureCookie(
      host, value, workaround_http_secure_cookies_, &should_allow_cookie);
  std::optional<net::CookiePartitionKey> cookie_partition_key =
      net::CookiePartitionKey::IsPartitioningDisabledInWebView()
          ? std::nullopt
          : std::make_optional(net::CookiePartitionKey::FromWire(
                net::SchemefulSite(new_host),
                net::CookiePartitionKey::AncestorChainBit::kSameSite));

  std::unique_ptr<net::CanonicalCookie> cc(net::CanonicalCookie::Create(
      new_host, value, base::Time::Now(), std::nullopt /* server_time */,
      cookie_partition_key, net::CookieSourceType::kOther,
      /*status=*/nullptr));

  if (!cc || !should_allow_cookie) {
    MaybeRunCookieCallback(std::move(callback), false);
    return;
  }

  // Note: CookieStore and network::CookieManager both accept a
  // CookieAccessResult callback. WebView only cares about boolean success,
  // which is why we use |AdaptCookieAccessResultToBool|. This is temporary
  // technical debt until we fully launch the Network Service code path.
  if (GetMojoCookieManager()) {
    // *cc.get() is safe, because network::CookieManager::SetCanonicalCookie
    // will make a copy before our smart pointer goes out of scope.
    GetMojoCookieManager()->SetCanonicalCookie(
        *cc.get(), new_host, net::CookieOptions::MakeAllInclusive(),
        base::BindOnce(net::cookie_util::IsCookieAccessResultInclude)
            .Then(std::move(callback)));
  } else {
    // Track this operation on the provisional store.
    pending_provisional_store_operations_++;
    auto wrapped_callback =
        base::BindOnce(net::cookie_util::IsCookieAccessResultInclude)
            .Then(std::move(callback))
            .Then(base::BindOnce(
                &CookieManager::OnProvisionalStoreOperationComplete,
                base::Unretained(this)));
    GetCookieStore()->SetCanonicalCookieAsync(
        std::move(cc), new_host, net::CookieOptions::MakeAllInclusive(),
        std::move(wrapped_callback), /*cookie_access_result=*/std::nullopt);
  }
}

std::string CookieManager::GetCookie(const std::string& url) {
  GURL host(url);

  net::CookieList cookie_list;
  ExecCookieTaskSync(base::BindOnce(&CookieManager::GetCookieListAsyncHelper,
                                    base::Unretained(this), host,
                                    &cookie_list));

  return net::CanonicalCookie::BuildCookieLine(cookie_list);
}

std::vector<std::string> CookieManager::GetCookieInfo(const std::string& url) {
  GURL host(url);

  net::CookieList cookie_list;
  ExecCookieTaskSync(base::BindOnce(&CookieManager::GetCookieListAsyncHelper,
                                    base::Unretained(this), host,
                                    &cookie_list));
  std::vector<std::string> cookie_attributes;
  for (net::CanonicalCookie cookie : cookie_list) {
    cookie_attributes.push_back(
        net::CanonicalCookie::BuildCookieAttributesLine(cookie));
  }
  return cookie_attributes;
}

void CookieManager::GetCookieListAsyncHelper(const GURL& host,
                                             net::CookieList* result,
                                             base::OnceClosure complete) {
  net::CookieOptions options = net::CookieOptions::MakeAllInclusive();

  // TODO(crbug.com/40188414): Complete partitioned cookies implementation for
  // WebView. The current implementation is a temporary fix for
  // crbug.com/1442333 to let the app access its 1p partitioned cookie.
  if (GetMojoCookieManager()) {
    GetMojoCookieManager()->GetCookieList(
        host, options,
        net::CookiePartitionKeyCollection(net::CookiePartitionKey::FromWire(
            net::SchemefulSite(host),
            net::CookiePartitionKey::AncestorChainBit::kSameSite)),
        base::BindOnce(&CookieManager::GetCookieListCompleted,
                       base::Unretained(this), std::move(complete), result));
  } else {
    // Track this operation on the provisional store.
    pending_provisional_store_operations_++;
    auto wrapped_complete = std::move(complete).Then(
        base::BindOnce(&CookieManager::OnProvisionalStoreOperationComplete,
                       base::Unretained(this)));
    GetCookieStore()->GetCookieListWithOptionsAsync(
        host, options,
        net::CookiePartitionKeyCollection(net::CookiePartitionKey::FromWire(
            net::SchemefulSite(host),
            net::CookiePartitionKey::AncestorChainBit::kSameSite)),
        base::BindOnce(&CookieManager::GetCookieListCompleted,
                       base::Unretained(this), std::move(wrapped_complete),
                       result));
  }
}

void CookieManager::GetCookieListCompleted(
    base::OnceClosure complete,
    net::CookieList* result,
    const net::CookieAccessResultList& value,
    const net::CookieAccessResultList& excluded_cookies) {
  *result = net::cookie_util::StripAccessResults(value);
  std::move(complete).Run();
}

void CookieManager::RemoveSessionCookies(
    JNIEnv* env,
    base::OnceCallback<void(bool)> callback) {
  ExecCookieTask(base::BindOnce(&CookieManager::RemoveSessionCookiesHelper,
                                base::Unretained(this), std::move(callback)));
}

void CookieManager::RemoveSessionCookiesSync(JNIEnv* env) {
  ExecCookieTaskSync(base::BindOnce(&CookieManager::RemoveSessionCookiesHelper,
                                    base::Unretained(this)));
}

void CookieManager::RemoveSessionCookiesHelper(
    base::OnceCallback<void(bool)> callback) {
  if (GetMojoCookieManager()) {
    auto match_session_cookies = network::mojom::CookieDeletionFilter::New();
    match_session_cookies->session_control =
        network::mojom::CookieDeletionSessionControl::SESSION_COOKIES;
    GetMojoCookieManager()->DeleteCookies(
        std::move(match_session_cookies),
        base::BindOnce(&CookieManager::RemoveCookiesCompleted,
                       base::Unretained(this), std::move(callback)));
  } else {
    // Track this operation on the provisional store.
    pending_provisional_store_operations_++;
    auto wrapped_callback = std::move(callback).Then(
        base::BindOnce(&CookieManager::OnProvisionalStoreOperationComplete,
                       base::Unretained(this)));
    GetCookieStore()->DeleteSessionCookiesAsync(
        base::BindOnce(&CookieManager::RemoveCookiesCompleted,
                       base::Unretained(this), std::move(wrapped_callback)));
  }
}

void CookieManager::RemoveCookiesCompleted(
    base::OnceCallback<void(bool)> callback,
    uint32_t num_deleted) {
  std::move(callback).Run(num_deleted > 0u);
}

void CookieManager::RemoveAllCookies(JNIEnv* env,
                                     base::OnceCallback<void(bool)> callback) {
  ExecCookieTask(base::BindOnce(&CookieManager::RemoveAllCookiesHelper,
                                base::Unretained(this), std::move(callback)));
}

void CookieManager::RemoveAllCookiesSync(JNIEnv* env) {
  ExecCookieTaskSync(base::BindOnce(&CookieManager::RemoveAllCookiesHelper,
                                    base::Unretained(this)));
}

void CookieManager::RemoveAllCookiesHelper(
    base::OnceCallback<void(bool)> callback) {
  // Clear client hints preferences when all cookies are cleared.
  should_clear_client_hints_cached_per_origin_map_ = true;
  if (GetMojoCookieManager()) {
    content::GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(
            &CookieManager::ClearClientHintsCachedPerOriginMapIfNeeded,
            base::Unretained(this)));
    // An empty filter matches all cookies.
    auto match_all_cookies = network::mojom::CookieDeletionFilter::New();
    GetMojoCookieManager()->DeleteCookies(
        std::move(match_all_cookies),
        base::BindOnce(&CookieManager::RemoveCookiesCompleted,
                       base::Unretained(this), std::move(callback)));
  } else {
    // TODO(crbug.com/40609350): Support clearing client hints here as well.
    // Track this operation on the provisional store.
    pending_provisional_store_operations_++;
    auto wrapped_callback = std::move(callback).Then(
        base::BindOnce(&CookieManager::OnProvisionalStoreOperationComplete,
                       base::Unretained(this)));
    GetCookieStore()->DeleteAllAsync(
        base::BindOnce(&CookieManager::RemoveCookiesCompleted,
                       base::Unretained(this), std::move(wrapped_callback)));
  }
}

void CookieManager::RemoveExpiredCookies(JNIEnv* env) {
  // HasCookies will call GetAllCookiesAsync, which in turn will force a GC.
  HasCookies(env);
}

void CookieManager::FlushCookieStore(JNIEnv* env) {
  ExecCookieTaskSync(base::BindOnce(&CookieManager::FlushCookieStoreAsyncHelper,
                                    base::Unretained(this)));
}

void CookieManager::FlushCookieStoreAsyncHelper(base::OnceClosure complete) {
  if (GetMojoCookieManager()) {
    GetMojoCookieManager()->FlushCookieStore(std::move(complete));
  } else {
    // Track this operation on the provisional store.
    pending_provisional_store_operations_++;
    auto wrapped_complete = std::move(complete).Then(
        base::BindOnce(&CookieManager::OnProvisionalStoreOperationComplete,
                       base::Unretained(this)));
    GetCookieStore()->FlushStore(std::move(wrapped_complete));
  }
}

bool CookieManager::HasCookies(JNIEnv* env) {
  bool has_cookies;
  ExecCookieTaskSync(base::BindOnce(&CookieManager::HasCookiesAsyncHelper,
                                    base::Unretained(this), &has_cookies));
  return has_cookies;
}

// TODO(kristianm): Simplify this, copying the entire list around
// should not be needed.
void CookieManager::HasCookiesAsyncHelper(bool* result,
                                          base::OnceClosure complete) {
  if (GetMojoCookieManager()) {
    GetMojoCookieManager()->GetAllCookies(
        base::BindOnce(&CookieManager::HasCookiesCompleted,
                       base::Unretained(this), std::move(complete), result));
  } else {
    // Track this operation on the provisional store.
    pending_provisional_store_operations_++;
    auto wrapped_complete = std::move(complete).Then(
        base::BindOnce(&CookieManager::OnProvisionalStoreOperationComplete,
                       base::Unretained(this)));
    GetCookieStore()->GetAllCookiesAsync(base::BindOnce(
        &CookieManager::HasCookiesCompleted, base::Unretained(this),
        std::move(wrapped_complete), result));
  }
}

void CookieManager::HasCookiesCompleted(base::OnceClosure complete,
                                        bool* result,
                                        const CookieList& cookies) {
  *result = cookies.size() != 0;
  std::move(complete).Run();
}

bool CookieManager::GetAllowFileSchemeCookies() {
  base::AutoLock lock(allow_file_scheme_cookies_lock_);
  return allow_file_scheme_cookies_;
}

bool CookieManager::GetAllowFileSchemeCookies(JNIEnv* env) {
  return GetAllowFileSchemeCookies();
}

void CookieManager::SetAllowFileSchemeCookies(JNIEnv* env, bool allow) {
  ExecCookieTaskSync(
      base::BindOnce(&CookieManager::SetAllowFileSchemeCookiesAsyncHelper,
                     base::Unretained(this), allow));
}

void CookieManager::SetAllowFileSchemeCookiesAsyncHelper(
    bool allow,
    base::OnceClosure complete) {
  DCHECK(cookie_store_task_runner_->RunsTasksInCurrentSequence());
  if (GetMojoCookieManager()) {
    GetMojoCookieManager()->AllowFileSchemeCookies(
        allow,
        base::BindOnce(&CookieManager::SetAllowFileSchemeCookiesCompleted,
                       base::Unretained(this), std::move(complete), allow));
  } else {
    // No tracking needed here: without a mojo cookie manager, this path
    // doesn't perform any async cookie store operation. It synchronously
    // updates allow_file_scheme_cookies_ via
    // SetAllowFileSchemeCookiesCompleted.
    bool can_change_schemes = !cookie_store_created_;
    SetAllowFileSchemeCookiesCompleted(std::move(complete), allow,
                                       can_change_schemes);
  }
}

void CookieManager::SetAllowFileSchemeCookiesCompleted(
    base::OnceClosure complete,
    bool allow,
    bool can_change_schemes) {
  // Should only update |allow_file_scheme_cookies_| if
  // SetAllowFileSchemeCookiesAsyncHelper said this is OK.
  if (can_change_schemes) {
    base::AutoLock lock(allow_file_scheme_cookies_lock_);
    allow_file_scheme_cookies_ = allow;
  }
  std::move(complete).Run();
}

void CookieManager::ClearClientHintsCachedPerOriginMapIfNeeded() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  // If we had a client hint cache clear pending, we should do it as soon as we
  // next check and see that the browser has been started.
  if (should_clear_client_hints_cached_per_origin_map_) {
    GetContext()->GetPrefService()->SetDict(
        prefs::kClientHintsCachedPerOriginMap, base::DictValue());
    should_clear_client_hints_cached_per_origin_map_ = false;
  }
}

static int64_t JNI_AwCookieManager_GetDefaultCookieManager(JNIEnv* env) {
  return reinterpret_cast<intptr_t>(CookieManager::GetDefaultInstance());
}

AwBrowserContext* CookieManager::GetContext() const {
  if (parent_context_) {
    return parent_context_;
  } else {
    return AwBrowserContext::GetDefault();
  }
}

base::FilePath CookieManager::GetContextPath() const {
  if (parent_context_) {
    // Non-default profile
    return parent_context_->GetPath();
  } else {
    // Default profile
    return AwBrowserContext::BuildStoragePath(
        base::FilePath(AwBrowserContextStore::kDefaultContextPath));
  }
}

static void JNI_AwCookieManager_DisablePartitionedCookies(JNIEnv* env) {
  net::CookiePartitionKey::DisablePartitioningInWebView();
}

}  // namespace android_webview

DEFINE_JNI(AwCookieManager)
