/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set sw=4 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_dom_ipc_Blob_h
#define mozilla_dom_ipc_Blob_h

#include "mozilla/Attributes.h"
#include "mozilla/dom/PBlobChild.h"
#include "mozilla/dom/PBlobParent.h"
#include "mozilla/dom/PBlobStreamChild.h"
#include "mozilla/dom/PBlobStreamParent.h"
#include "mozilla/dom/PContent.h"

#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"

class nsIDOMBlob;
class nsIIPCSerializableInputStream;
class nsIInputStream;

namespace mozilla {
namespace dom {
namespace ipc {

enum ActorFlavorEnum
{
  Parent = 0,
  Child
};

template <ActorFlavorEnum>
struct BlobTraits
{ };

template <>
struct BlobTraits<Parent>
{
  typedef mozilla::dom::PBlobParent ProtocolType;
  typedef mozilla::dom::PBlobStreamParent StreamType;

  // BaseType on the parent side is a bit more complicated than for the child
  // side. In the case of nsIInputStreams backed by files we need to ensure that
  // the files are actually opened and closed on a background thread before we
  // can send their file handles across to the child. The child process could
  // crash during this process so we need to make sure we cancel the intended
  // response in such a case. We do that by holding an array of
  // nsRevocableEventPtr. If the child crashes then this actor will be destroyed
  // and the nsRevocableEventPtr destructor will cancel any stream events that
  // are currently in flight.
  class BaseType : public ProtocolType
  {
  protected:
    BaseType()
    { }

    virtual ~BaseType()
    { }

    // Each instance of this class will be dispatched to the network stream
    // thread pool to run the first time where it will open the file input
    // stream. It will then dispatch itself back to the main thread to send the
    // child process its response (assuming that the child has not crashed). The
    // runnable will then dispatch itself to the thread pool again in order to
    // close the file input stream.
    class OpenStreamRunnable : public nsRunnable
    {
      friend class nsRevocableEventPtr<OpenStreamRunnable>;
    public:
      NS_DECL_NSIRUNNABLE

      OpenStreamRunnable(BaseType* aOwner, StreamType* aActor,
                         nsIInputStream* aStream,
                         nsIIPCSerializableInputStream* aSerializable,
                         nsIEventTarget* aTarget);

    private:
#ifdef DEBUG
      void
      Revoke();
#else
      void
      Revoke()
      {
        mRevoked = true;
      }
#endif

      // Only safe to access these two pointers if mRevoked is false!
      BaseType* mOwner;
      StreamType* mActor;

      nsCOMPtr<nsIInputStream> mStream;
      nsCOMPtr<nsIIPCSerializableInputStream> mSerializable;
      nsCOMPtr<nsIEventTarget> mTarget;

      bool mRevoked;
      bool mClosing;
    };

    friend class OpenStreamRunnable;

    void
    NoteRunnableCompleted(OpenStreamRunnable* aRunnable);

    nsTArray<nsRevocableEventPtr<OpenStreamRunnable> > mOpenStreamRunnables;
  };
};

template <>
struct BlobTraits<Child>
{
  typedef mozilla::dom::PBlobChild ProtocolType;
  typedef mozilla::dom::PBlobStreamChild StreamType;

  class BaseType : public ProtocolType
  {
  protected:
    BaseType()
    { }

    virtual ~BaseType()
    { }
  };
};

template <ActorFlavorEnum>
class RemoteBlob;

template <ActorFlavorEnum ActorFlavor>
class Blob : public BlobTraits<ActorFlavor>::BaseType
{
  friend class RemoteBlob<ActorFlavor>;

public:
  typedef typename BlobTraits<ActorFlavor>::ProtocolType ProtocolType;
  typedef typename BlobTraits<ActorFlavor>::StreamType StreamType;
  typedef typename BlobTraits<ActorFlavor>::BaseType BaseType;
  typedef RemoteBlob<ActorFlavor> RemoteBlobType;
  typedef mozilla::ipc::IProtocolManager<
                      mozilla::ipc::RPCChannel::RPCListener>::ActorDestroyReason
          ActorDestroyReason;
  typedef mozilla::dom::BlobConstructorParams BlobConstructorParams;

protected:
  nsIDOMBlob* mBlob;
  RemoteBlobType* mRemoteBlob;
  bool mOwnsBlob;
  bool mBlobIsFile;

public:
  // This create function is called on the sending side.
  static Blob*
  Create(nsIDOMBlob* aBlob)
  {
    return new Blob(aBlob);
  }

  // This create function is called on the receiving side.
  static Blob*
  Create(const BlobConstructorParams& aParams);

  // Get the blob associated with this actor. This may always be called on the
  // sending side. It may also be called on the receiving side unless this is a
  // "mystery" blob that has not yet received a SetMysteryBlobInfo() call.
  already_AddRefed<nsIDOMBlob>
  GetBlob();

  // Use this for files.
  bool
  SetMysteryBlobInfo(const nsString& aName, const nsString& aContentType,
                     uint64_t aLength);

  // Use this for non-file blobs.
  bool
  SetMysteryBlobInfo(const nsString& aContentType, uint64_t aLength);

private:
  // This constructor is called on the sending side.
  Blob(nsIDOMBlob* aBlob);

  // This constructor is called on the receiving side.
  Blob(const BlobConstructorParams& aParams);

  void
  SetRemoteBlob(nsRefPtr<RemoteBlobType>& aRemoteBlob);

  void
  NoteDyingRemoteBlob();

  // These methods are only called by the IPDL message machinery.
  virtual void
  ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;

  virtual bool
  RecvResolveMystery(const ResolveMysteryParams& aParams) MOZ_OVERRIDE;

  virtual bool
  RecvPBlobStreamConstructor(StreamType* aActor) MOZ_OVERRIDE;

  virtual StreamType*
  AllocPBlobStream() MOZ_OVERRIDE;

  virtual bool
  DeallocPBlobStream(StreamType* aActor) MOZ_OVERRIDE;
};

} // namespace ipc

typedef mozilla::dom::ipc::Blob<mozilla::dom::ipc::Child> BlobChild;
typedef mozilla::dom::ipc::Blob<mozilla::dom::ipc::Parent> BlobParent;

} // namespace dom
} // namespace mozilla

#endif // mozilla_dom_ipc_Blob_h
