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

#ifndef DisplayItemClient_h
#define DisplayItemClient_h

#include "platform/PlatformExport.h"
#include "platform/geometry/LayoutRect.h"
#include "platform/graphics/PaintInvalidationReason.h"
#include "wtf/Assertions.h"
#include "wtf/text/WTFString.h"

#define CHECK_DISPLAY_ITEM_CLIENT_ALIVENESS DCHECK_IS_ON()

namespace blink {

// The class for objects that can be associated with display items. A
// DisplayItemClient object should live at least longer than the document cycle
// in which its display items are created during painting. After the document
// cycle, a pointer/reference to DisplayItemClient should be no longer
// dereferenced unless we can make sure the client is still valid.
class PLATFORM_EXPORT DisplayItemClient {
 public:
#if CHECK_DISPLAY_ITEM_CLIENT_ALIVENESS
  DisplayItemClient();
  virtual ~DisplayItemClient();

  // Tests if this DisplayItemClient object has been created and has not been
  // deleted yet.
  bool isAlive() const;

  // Called when any DisplayItem of this DisplayItemClient is added into
  // PaintController using PaintController::createAndAppend() or into a cached
  // subsequence.
  void beginShouldKeepAlive(const void* owner) const;

  // Called when the DisplayItemClient is sure that it can safely die before its
  // owners have chance to remove it from the aliveness control.
  void endShouldKeepAlive() const;

  // Clears all should-keep-alive DisplayItemClients of a PaintController.
  // Called after PaintController commits new display items or the subsequence
  // owner is invalidated.
  static void endShouldKeepAliveAllClients(const void* owner);
  static void endShouldKeepAliveAllClients();
#else
  virtual ~DisplayItemClient() {}
#endif

  virtual String debugName() const = 0;

  // The visual rect of this DisplayItemClient, in the object space of the
  // object that owns the GraphicsLayer, i.e. offset by
  // offsetFromLayoutObjectWithSubpixelAccumulation().
  virtual LayoutRect visualRect() const = 0;

  void setDisplayItemsUncached(
      PaintInvalidationReason reason = PaintInvalidationFull) const {
    m_cacheGenerationOrInvalidationReason.invalidate(reason);
#if CHECK_DISPLAY_ITEM_CLIENT_ALIVENESS
    // Clear should-keep-alive of DisplayItemClients in a subsequence if this
    // object is a subsequence.
    endShouldKeepAliveAllClients(this);
#endif
  }

  PaintInvalidationReason getPaintInvalidationReason() const {
    return m_cacheGenerationOrInvalidationReason.getPaintInvalidationReason();
  }

  // A client is considered "just created" if its display items have never been
  // committed.
  bool isJustCreated() const {
    return m_cacheGenerationOrInvalidationReason.isJustCreated();
  }
  void clearIsJustCreated() const {
    m_cacheGenerationOrInvalidationReason.clearIsJustCreated();
  }

 private:
  friend class FakeDisplayItemClient;
  friend class PaintController;

  // Holds a unique cache generation id of DisplayItemClients and
  // PaintControllers, or PaintInvalidationReason if the DisplayItemClient or
  // PaintController is invalidated.
  //
  // A paint controller sets its cache generation to
  // DisplayItemCacheGeneration::next() at the end of each
  // commitNewDisplayItems, and updates the cache generation of each client with
  // cached drawings by calling DisplayItemClient::setDisplayItemsCached(). A
  // display item is treated as validly cached in a paint controller if its
  // cache generation matches the paint controller's cache generation.
  //
  // SPv1 only: If a display item is painted on multiple paint controllers,
  // because cache generations are unique, the client's cache generation matches
  // the last paint controller only. The client will be treated as invalid on
  // other paint controllers regardless if it's validly cached by these paint
  // controllers. This situation is very rare (about 0.07% of clients were
  // painted on multiple paint controllers) so the performance penalty is
  // trivial.
  class PLATFORM_EXPORT CacheGenerationOrInvalidationReason {
    DISALLOW_NEW();

   public:
    CacheGenerationOrInvalidationReason() : m_value(kJustCreated) {}

    void invalidate(PaintInvalidationReason reason = PaintInvalidationFull) {
      if (m_value != kJustCreated)
        m_value = static_cast<ValueType>(reason);
    }

    static CacheGenerationOrInvalidationReason next() {
      // In case the value overflowed in the previous call.
      if (s_nextGeneration < kFirstValidGeneration)
        s_nextGeneration = kFirstValidGeneration;
      return CacheGenerationOrInvalidationReason(s_nextGeneration++);
    }

    bool matches(const CacheGenerationOrInvalidationReason& other) const {
      return m_value >= kFirstValidGeneration &&
             other.m_value >= kFirstValidGeneration && m_value == other.m_value;
    }

    PaintInvalidationReason getPaintInvalidationReason() const {
      return m_value < kJustCreated
                 ? static_cast<PaintInvalidationReason>(m_value)
                 : PaintInvalidationNone;
    }

    bool isJustCreated() const { return m_value == kJustCreated; }
    void clearIsJustCreated() {
      m_value = static_cast<ValueType>(PaintInvalidationFull);
    }

   private:
    typedef uint32_t ValueType;
    explicit CacheGenerationOrInvalidationReason(ValueType value)
        : m_value(value) {}

    static const ValueType kJustCreated =
        static_cast<ValueType>(PaintInvalidationReasonMax) + 1;
    static const ValueType kFirstValidGeneration =
        static_cast<ValueType>(PaintInvalidationReasonMax) + 2;
    static ValueType s_nextGeneration;
    ValueType m_value;
  };

  bool displayItemsAreCached(CacheGenerationOrInvalidationReason other) const {
    return m_cacheGenerationOrInvalidationReason.matches(other);
  }
  void setDisplayItemsCached(
      CacheGenerationOrInvalidationReason cacheGeneration) const {
    m_cacheGenerationOrInvalidationReason = cacheGeneration;
  }

  mutable CacheGenerationOrInvalidationReason
      m_cacheGenerationOrInvalidationReason;
};

inline bool operator==(const DisplayItemClient& client1,
                       const DisplayItemClient& client2) {
  return &client1 == &client2;
}
inline bool operator!=(const DisplayItemClient& client1,
                       const DisplayItemClient& client2) {
  return &client1 != &client2;
}

}  // namespace blink

#endif  // DisplayItemClient_h
