/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_INDEXEDDB_IDB_TRANSACTION_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_INDEXEDDB_IDB_TRANSACTION_H_

#include <memory>

#include "base/dcheck_is_on.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-blink-forward.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_idb_transaction_mode.h"
#include "third_party/blink/renderer/core/dom/dom_string_list.h"
#include "third_party/blink/renderer/core/dom/events/event_listener.h"
#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
#include "third_party/blink/renderer/modules/event_modules.h"
#include "third_party/blink/renderer/modules/event_target_modules.h"
#include "third_party/blink/renderer/modules/indexeddb/idb_metadata.h"
#include "third_party/blink/renderer/modules/indexeddb/indexed_db.h"
#include "third_party/blink/renderer/modules/modules_export.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_linked_hash_set.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_associated_remote.h"
#include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h"
#include "third_party/blink/renderer/platform/wtf/deque.h"
#include "third_party/blink/renderer/platform/wtf/hash_set.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"

namespace blink {

class DOMException;
class ExecutionContext;
class ExceptionState;
class IDBDatabase;
class IDBIndex;
class IDBObjectStore;
class IDBOpenDBRequest;
class IDBRequest;
class IDBRequestQueueItem;
class ScriptState;
class V8IDBTransactionDurability;

class MODULES_EXPORT IDBTransaction final
    : public EventTarget,
      public ActiveScriptWrappable<IDBTransaction>,
      public ExecutionContextLifecycleObserver {
  DEFINE_WRAPPERTYPEINFO();

 public:
  // `Commit` can be called after the context is destroyed, so at least for now
  // the mojo connection has to outlive context destruction.
  using TransactionMojoRemote = HeapMojoAssociatedRemote<
      mojom::blink::IDBTransaction,
      HeapMojoWrapperMode::kForceWithoutContextObserver>;

  static IDBTransaction* CreateNonVersionChange(
      ScriptState* script_state,
      TransactionMojoRemote remote,
      int64_t transaction_id,
      const HashSet<String>& scope,
      mojom::blink::IDBTransactionMode,
      mojom::blink::IDBTransactionDurability,
      IDBDatabase* database);
  static IDBTransaction* CreateVersionChange(
      ExecutionContext*,
      TransactionMojoRemote remote,
      int64_t transaction_id,
      IDBDatabase*,
      IDBOpenDBRequest*,
      const IDBDatabaseMetadata& old_metadata);

  // For non-upgrade transactions.
  IDBTransaction(ScriptState*,
                 TransactionMojoRemote remote,
                 int64_t,
                 const HashSet<String>& scope,
                 mojom::blink::IDBTransactionMode,
                 mojom::blink::IDBTransactionDurability,
                 IDBDatabase*);
  // For upgrade transactions.
  IDBTransaction(ExecutionContext*,
                 TransactionMojoRemote remote,
                 int64_t,
                 IDBDatabase*,
                 IDBOpenDBRequest*,
                 const IDBDatabaseMetadata&);
  ~IDBTransaction() override;

  void Trace(Visitor*) const override;

  static mojom::blink::IDBTransactionMode EnumToMode(
      V8IDBTransactionMode::Enum);

  int64_t Id() const { return id_; }
  bool IsActive() const { return state_ == kActive; }
  bool IsFinished() const { return state_ == kFinished; }
  bool IsFinishing() const {
    return state_ == kCommitting || state_ == kAborting;
  }
  bool IsReadOnly() const {
    return mode_ == mojom::blink::IDBTransactionMode::ReadOnly;
  }
  bool IsVersionChange() const {
    return mode_ == mojom::blink::IDBTransactionMode::VersionChange;
  }
  int64_t NumErrorsHandled() const { return num_errors_handled_; }
  void IncrementNumErrorsHandled() { ++num_errors_handled_; }

  // Implement the IDBTransaction IDL
  V8IDBTransactionMode mode() const;
  V8IDBTransactionDurability durability() const;
  DOMStringList* objectStoreNames() const;
  IDBDatabase& db() { return *database_; }
  DOMException* error() const { return error_.Get(); }
  IDBObjectStore* objectStore(const String& name, ExceptionState&);
  void abort(ExceptionState&);
  void commit(ExceptionState&);

  void RegisterRequest(IDBRequest*);
  void UnregisterRequest(IDBRequest*);

  // True if this transaction has at least one request whose result is being
  // post-processed.
  //
  // While this is true, new results must be queued using EnqueueResult().
  inline bool HasQueuedResults() const { return !result_queue_.empty(); }
  void EnqueueResult(std::unique_ptr<IDBRequestQueueItem> result);
  // Called when a result's post-processing has completed.
  void OnResultReady();

  // The methods below are called right before the changes are applied to the
  // database's metadata. We use this unusual sequencing because some of the
  // methods below need to access the metadata values before the change, and
  // following the same lifecycle for all methods makes the code easier to
  // reason about.
  void ObjectStoreCreated(const String& name, IDBObjectStore*);
  void ObjectStoreDeleted(const int64_t object_store_id, const String& name);
  void ObjectStoreRenamed(const String& old_name, const String& new_name);
  // Called when deleting an index whose IDBIndex had been created.
  void IndexDeleted(IDBIndex*);

  // Called during event dispatch.
  //
  // This can trigger transaction auto-commit.
  void SetActive(bool new_is_active);

  // Called right before and after structured serialization.
  void SetActiveDuringSerialization(bool new_is_active);

  void SetError(DOMException*);

  DEFINE_ATTRIBUTE_EVENT_LISTENER(abort, kAbort)
  DEFINE_ATTRIBUTE_EVENT_LISTENER(complete, kComplete)
  DEFINE_ATTRIBUTE_EVENT_LISTENER(error, kError)

  // The backend aborted/completed.
  void OnAbort(DOMException*);
  void OnComplete();

  // Start aborting if not already aborting. This is called when either the
  // site initiates the abort via `abort()`, or frontend logic necessitates an
  // abort.
  void StartAborting(DOMException* error, bool from_frontend = true);

  // Methods that operate on the backend.
  void CreateObjectStore(int64_t object_store_id,
                         const String& name,
                         const IDBKeyPath&,
                         bool auto_increment);
  void DeleteObjectStore(int64_t object_store_id);
  void Put(int64_t object_store_id,
           std::unique_ptr<IDBValue> value,
           std::unique_ptr<IDBKey> primary_key,
           mojom::blink::IDBPutMode put_mode,
           Vector<IDBIndexKeys> index_keys,
           mojom::blink::IDBTransaction::PutCallback callback);
  void SetIndexKeys(int64_t object_store_id,
                    std::unique_ptr<IDBKey> primary_key,
                    IDBIndexKeys);
  void SetIndexReady(int64_t object_store_id);
  void FlushForTesting();

  // EventTarget
  const AtomicString& InterfaceName() const override;
  ExecutionContext* GetExecutionContext() const override;

  // ScriptWrappable
  bool HasPendingActivity() const final;

  // ExecutionContextLifecycleObserver
  void ContextDestroyed() override {}

  // For use in IDBObjectStore::IsNewlyCreated(). The rest of the code should
  // use IDBObjectStore::IsNewlyCreated() instead of calling this method
  // directly.
  int64_t OldMaxObjectStoreId() const {
    DCHECK(IsVersionChange());
    return old_database_metadata_.max_object_store_id;
  }

  // Returns a detailed message to use when throwing TransactionInactiveError,
  // depending on whether the transaction is just inactive or has finished.
  const char* InactiveErrorMessage() const;

  void set_max_put_value_size_for_testing(size_t size) {
    max_put_value_size_override_ = size;
  }

 protected:
  // EventTarget
  DispatchEventResult DispatchEventInternal(Event&) override;

 private:
  using IDBObjectStoreMap = HeapHashMap<String, Member<IDBObjectStore>>;

  // Maximum size (in bytes) of value/key pair allowed for put requests. Any
  // requests larger than this size will be rejected.
  // Used by unit tests to exercise behavior without allocating huge chunks
  // of memory.
  std::optional<size_t> max_put_value_size_override_;

  // Called when a transaction is aborted.
  void AbortOutstandingRequests(bool queue_tasks);
  void RevertDatabaseMetadata();

  // Called when a transaction is completed (committed or aborted).
  void Finished();

  enum State {
    kInactive,    // Created or started, but not in an event callback.
    kActive,      // Created or started, in creation scope or an event callback.
    kCommitting,  // In the process of completing.
    kAborting,    // In the process of aborting.
    kFinished,    // No more events will fire and no new requests may be filed.
  };

  TransactionMojoRemote remote_;

  const int64_t id_;
  Member<IDBDatabase> database_;
  Member<IDBOpenDBRequest> open_db_request_;
  const mojom::blink::IDBTransactionMode mode_;
  const mojom::blink::IDBTransactionDurability durability_;

  // The names of the object stores that make up this transaction's scope.
  //
  // Transactions may not access object stores outside their scope.
  //
  // The scope of versionchange transactions is the entire database. We
  // represent this case with an empty |scope_|, because copying all the store
  // names would waste both time and memory.
  //
  // Using object store names to represent a transaction's scope is safe
  // because object stores cannot be renamed in non-versionchange
  // transactions.
  const HashSet<String> scope_;

  // The initial state depends on the type of transaction --- see constructors.
  State state_;
  bool has_pending_activity_ = true;
  int64_t num_errors_handled_ = 0;
  Member<DOMException> error_;

  HeapLinkedHashSet<Member<IDBRequest>> request_list_;

  // The IDBRequest results whose events have not been enqueued yet.
  //
  // When a result requires post-processing, such as large value unwrapping, it
  // is queued up until post-processing completes. All the results that arrive
  // during the post-processing phase are also queued up, so their result events
  // are fired in the order in which the requests were performed, as prescribed
  // by the IndexedDB specification.
  Deque<std::unique_ptr<IDBRequestQueueItem>> result_queue_;

#if DCHECK_IS_ON()
  bool finish_called_ = false;
#endif  // DCHECK_IS_ON()

  // Whether `this` is already inside a call to `OnResultReady`.
  bool handling_ready_ = false;

  // Caches the IDBObjectStore instances returned by the objectStore() method.
  //
  // The spec requires that a transaction's objectStore() returns the same
  // IDBObjectStore instance for a specific store, so this cache is necessary
  // for correctness.
  //
  // objectStore() throws for completed/aborted transactions, so this is not
  // used after a transaction is finished, and can be cleared.
  IDBObjectStoreMap object_store_map_;

  // The metadata of object stores when they are opened by this transaction.
  //
  // Only valid for versionchange transactions.
  HeapHashMap<Member<IDBObjectStore>, scoped_refptr<IDBObjectStoreMetadata>>
      old_store_metadata_;

  // The metadata of deleted object stores without IDBObjectStore instances.
  //
  // Only valid for versionchange transactions.
  Vector<scoped_refptr<IDBObjectStoreMetadata>> deleted_object_stores_;

  // Tracks the indexes deleted by this transaction.
  //
  // This set only includes indexes that were created before this transaction,
  // and were deleted during this transaction. Once marked for deletion, these
  // indexes are removed from their object stores' index maps, so we need to
  // stash them somewhere else in case the transaction gets aborted.
  //
  // This set does not include indexes created and deleted during this
  // transaction, because we don't need to change their metadata when the
  // transaction aborts, as they will still be marked for deletion.
  //
  // Only valid for versionchange transactions.
  HeapVector<Member<IDBIndex>> deleted_indexes_;

  // Shallow snapshot of the database metadata when the transaction starts.
  //
  // This does not include a snapshot of the database's object store / index
  // metadata.
  //
  // Only valid for versionchange transactions.
  IDBDatabaseMetadata old_database_metadata_;
};

}  // namespace blink

#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_INDEXEDDB_IDB_TRANSACTION_H_
