/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 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 _include_mozilla_gfx_ipc_CrossProcessPaint_h_
#define _include_mozilla_gfx_ipc_CrossProcessPaint_h_

#include "nsISupportsImpl.h"

#include "mozilla/dom/ipc/IdType.h"
#include "mozilla/gfx/Point.h"
#include "mozilla/gfx/Rect.h"
#include "mozilla/MozPromise.h"
#include "mozilla/ipc/ByteBuf.h"
#include "nsColor.h"
#include "nsTHashMap.h"
#include "nsHashKeys.h"
#include "nsRefPtrHashtable.h"
#include "nsTHashSet.h"

class nsIDocShell;

namespace IPC {
template <typename T>
struct ParamTraits;
}  // namespace IPC

namespace mozilla {

namespace dom {
class CanonicalBrowsingContext;
class DOMRect;
class Promise;
class WindowGlobalParent;
}  // namespace dom

namespace gfx {

class CrossProcessPaint;

enum class CrossProcessPaintFlags {
  None = 0,
  DrawView = 1 << 1,
  ResetScrollPosition = 1 << 2,
  UseHighQualityScaling = 1 << 3,
};

MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CrossProcessPaintFlags)

/**
 * A fragment of a paint of a cross process document tree.
 */
class PaintFragment final {
 public:
  /// Initializes an empty PaintFragment
  PaintFragment() = default;

  /**
   * Creates a paint fragment by recording the draw commands and dependent tabs
   * for a BrowsingContext.
   *
   * @param aBrowsingContext The frame to record.
   * @param aRect The rectangle relative to the viewport to use. If no
   *   rectangle is specified, then the whole viewport will be used.
   * @param aScale The coordinate scale to use. The size of the resolved
   *   surface will be `aRect.Size() * aScale`, with aScale clamped to
   *   at least kMinPaintScale.
   * @param aBackgroundColor The background color to use.
   *
   * @return A paint fragment. The paint fragment may be `empty` if rendering
   *         was unable to be accomplished for some reason.
   */
  static PaintFragment Record(dom::BrowsingContext* aBc,
                              const Maybe<IntRect>& aRect, float aScale,
                              nscolor aBackgroundColor,
                              CrossProcessPaintFlags aFlags);

  /// Returns whether this paint fragment contains a valid recording.
  bool IsEmpty() const;

  PaintFragment(PaintFragment&&) = default;
  PaintFragment& operator=(PaintFragment&&) = default;

 protected:
  friend struct IPC::ParamTraits<PaintFragment>;
  friend CrossProcessPaint;

  typedef mozilla::ipc::ByteBuf ByteBuf;

  PaintFragment(IntSize, ByteBuf&&, nsTHashSet<uint64_t>&&);

  IntSize mSize;
  ByteBuf mRecording;
  nsTHashSet<uint64_t> mDependencies;
};

struct RecordedDependentSurface;

/**
 * An object for painting a cross process document tree.
 */
class CrossProcessPaint final {
  NS_INLINE_DECL_REFCOUNTING(CrossProcessPaint);

 public:
  typedef nsRefPtrHashtable<nsUint64HashKey, RecordedDependentSurface>
      ResolvedFragmentMap;
  typedef MozPromise<ResolvedFragmentMap, nsresult, true> ResolvePromise;
  /**
   * Begin an asynchronous paint of a cross process document tree starting at
   * a WindowGlobalParent. A maybe-async paint for the root WGP will be done,
   * then async paints will be recursively queued for remote subframes. Once
   * all subframes have been recorded, the final image will be resolved, and
   * the promise will be resolved with a dom::ImageBitmap.
   *
   * @param aRoot The WindowGlobalParent to paint.
   * @param aRect The rectangle relative to the viewport to use, or null to
   *   render the whole viewport.
   * @param aScale The coordinate scale to use. The size of the resolved
   *   surface will be `aRect.Size() * aScale`, with aScale clamped to
   *   at least kMinPaintScale. See the implementation for the current
   *   minimum value.
   * @param aBackgroundColor The background color to use.
   * @param aPromise The promise to resolve with a dom::ImageBitmap.
   *
   * @returns Whether the paint was able to be initiated or not.
   */
  static bool Start(dom::WindowGlobalParent* aRoot, const dom::DOMRect* aRect,
                    float aScale, nscolor aBackgroundColor,
                    CrossProcessPaintFlags aFlags, dom::Promise* aPromise);

  static RefPtr<ResolvePromise> Start(nsTHashSet<uint64_t>&& aDependencies);

  void ReceiveFragment(dom::WindowGlobalParent* aWGP,
                       PaintFragment&& aFragment);
  void LostFragment(dom::WindowGlobalParent* aWGP);

 private:
  typedef nsTHashMap<nsUint64HashKey, PaintFragment> ReceivedFragmentMap;

  CrossProcessPaint(float aScale, dom::TabId aRoot,
                    CrossProcessPaintFlags aFlags);
  ~CrossProcessPaint();

  void QueueDependencies(const nsTHashSet<uint64_t>& aDependencies);

  void QueuePaint(
      dom::WindowGlobalParent* aWGP, const Maybe<IntRect>& aRect,
      nscolor aBackgroundColor = NS_RGBA(0, 0, 0, 0),
      CrossProcessPaintFlags aFlags = CrossProcessPaintFlags::DrawView);

  void QueuePaint(dom::CanonicalBrowsingContext* aBc);

  /// Clear the state of this paint so that it cannot be resolved or receive
  /// any paint fragments.
  void Clear(nsresult aStatus);

  /// Returns if this paint has been cleared.
  bool IsCleared() const;

  /// Resolves the paint fragments if we have none pending and resolves the
  /// promise.
  void MaybeResolve();
  nsresult ResolveInternal(dom::TabId aTabId, ResolvedFragmentMap* aResolved);

  RefPtr<ResolvePromise> Init() {
    MOZ_ASSERT(mPromise.IsEmpty());
    return mPromise.Ensure(__func__);
  }

  // UseHighQualityScaling is the only flag that dependencies inherit, and we
  // always want to use DrawView for dependencies.
  CrossProcessPaintFlags GetFlagsForDependencies() const {
    return (mFlags & CrossProcessPaintFlags::UseHighQualityScaling) |
           CrossProcessPaintFlags::DrawView;
  }

  MozPromiseHolder<ResolvePromise> mPromise;
  dom::TabId mRoot;
  float mScale;
  uint32_t mPendingFragments;
  ReceivedFragmentMap mReceivedFragments;
  CrossProcessPaintFlags mFlags;
};

}  // namespace gfx
}  // namespace mozilla

#endif  // _include_mozilla_gfx_ipc_CrossProcessPaint_h_
