/*
 * Copyright (C) 2013 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:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * 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.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
 * OWNER OR 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 DocumentLifecycle_h
#define DocumentLifecycle_h

#include "core/CoreExport.h"
#include "wtf/Allocator.h"
#include "wtf/Assertions.h"
#include "wtf/Noncopyable.h"

namespace blink {

class CORE_EXPORT DocumentLifecycle {
  DISALLOW_NEW();
  WTF_MAKE_NONCOPYABLE(DocumentLifecycle);

 public:
  enum LifecycleState {
    Uninitialized,
    Inactive,

    // When the document is active, it traverses these states.

    VisualUpdatePending,

    InStyleRecalc,
    StyleClean,

    InLayoutSubtreeChange,
    LayoutSubtreeChangeClean,

    InPreLayout,
    InPerformLayout,
    AfterPerformLayout,
    LayoutClean,

    InCompositingUpdate,
    CompositingClean,

    InPaintInvalidation,
    PaintInvalidationClean,

    // In InPrePaint step, any data needed by painting are prepared.
    // When RuntimeEnabledFeatures::slimmingPaintV2Enabled, paint property trees
    // are built.
    // Otherwise these steps are not applicable.
    InPrePaint,
    PrePaintClean,

    InPaint,
    PaintClean,

    // Once the document starts shutting down, we cannot return
    // to the style/layout/compositing states.
    Stopping,
    Stopped,
  };

  class Scope {
    STACK_ALLOCATED();
    WTF_MAKE_NONCOPYABLE(Scope);

   public:
    Scope(DocumentLifecycle&, LifecycleState finalState);
    ~Scope();

   private:
    DocumentLifecycle& m_lifecycle;
    LifecycleState m_finalState;
  };

  class DeprecatedTransition {
    DISALLOW_NEW();
    WTF_MAKE_NONCOPYABLE(DeprecatedTransition);

   public:
    DeprecatedTransition(LifecycleState from, LifecycleState to);
    ~DeprecatedTransition();

    LifecycleState from() const { return m_from; }
    LifecycleState to() const { return m_to; }

   private:
    DeprecatedTransition* m_previous;
    LifecycleState m_from;
    LifecycleState m_to;
  };

  // Within this scope, state transitions are not allowed.
  // Any attempts to advance or rewind will result in a DCHECK.
  class DisallowTransitionScope {
    STACK_ALLOCATED();
    WTF_MAKE_NONCOPYABLE(DisallowTransitionScope);

   public:
    explicit DisallowTransitionScope(DocumentLifecycle& documentLifecycle)
        : m_documentLifecycle(documentLifecycle) {
      m_documentLifecycle.incrementNoTransitionCount();
    }

    ~DisallowTransitionScope() {
      m_documentLifecycle.decrementNoTransitionCount();
    }

   private:
    DocumentLifecycle& m_documentLifecycle;
  };

  class DetachScope {
    STACK_ALLOCATED();
    WTF_MAKE_NONCOPYABLE(DetachScope);

   public:
    explicit DetachScope(DocumentLifecycle& documentLifecycle)
        : m_documentLifecycle(documentLifecycle) {
      m_documentLifecycle.incrementDetachCount();
    }

    ~DetachScope() { m_documentLifecycle.decrementDetachCount(); }

   private:
    DocumentLifecycle& m_documentLifecycle;
  };

  // Throttling is disabled by default. Instantiating this class allows
  // throttling (e.g., during BeginMainFrame). If a script needs to run inside
  // this scope, DisallowThrottlingScope should be used to let the script
  // perform a synchronous layout if necessary.
  class CORE_EXPORT AllowThrottlingScope {
    STACK_ALLOCATED();
    WTF_MAKE_NONCOPYABLE(AllowThrottlingScope);

   public:
    AllowThrottlingScope(DocumentLifecycle&);
    ~AllowThrottlingScope();
  };

  class CORE_EXPORT DisallowThrottlingScope {
    STACK_ALLOCATED();
    WTF_MAKE_NONCOPYABLE(DisallowThrottlingScope);

   public:
    DisallowThrottlingScope(DocumentLifecycle&);
    ~DisallowThrottlingScope();

   private:
    int m_savedCount;
  };

  DocumentLifecycle();
  ~DocumentLifecycle();

  bool isActive() const { return m_state > Inactive && m_state < Stopping; }
  LifecycleState state() const { return m_state; }

  bool stateAllowsTreeMutations() const;
  bool stateAllowsLayoutTreeMutations() const;
  bool stateAllowsDetach() const;
  bool stateAllowsLayoutInvalidation() const;
  bool stateAllowsLayoutTreeNotifications() const;

  void advanceTo(LifecycleState);
  void ensureStateAtMost(LifecycleState);

  bool stateTransitionDisallowed() const { return m_disallowTransitionCount; }
  void incrementNoTransitionCount() { m_disallowTransitionCount++; }
  void decrementNoTransitionCount() {
    DCHECK_GT(m_disallowTransitionCount, 0);
    m_disallowTransitionCount--;
  }

  bool inDetach() const { return m_detachCount; }
  void incrementDetachCount() { m_detachCount++; }
  void decrementDetachCount() {
    DCHECK_GT(m_detachCount, 0);
    m_detachCount--;
  }

  bool throttlingAllowed() const;

 private:
#if DCHECK_IS_ON()
  static const char* stateAsDebugString(const LifecycleState);
  bool canAdvanceTo(LifecycleState) const;
  bool canRewindTo(LifecycleState) const;
#endif

  LifecycleState m_state;
  int m_detachCount;
  int m_disallowTransitionCount;
};

inline bool DocumentLifecycle::stateAllowsTreeMutations() const {
  // FIXME: We should not allow mutations in InPreLayout or AfterPerformLayout
  // either, but we need to fix MediaList listeners and plugins first.
  return m_state != InStyleRecalc && m_state != InPerformLayout &&
         m_state != InCompositingUpdate && m_state != InPrePaint &&
         m_state != InPaint;
}

inline bool DocumentLifecycle::stateAllowsLayoutTreeMutations() const {
  return m_detachCount || m_state == InStyleRecalc ||
         m_state == InLayoutSubtreeChange;
}

inline bool DocumentLifecycle::stateAllowsLayoutTreeNotifications() const {
  return m_state == InLayoutSubtreeChange;
}

inline bool DocumentLifecycle::stateAllowsDetach() const {
  return m_state == VisualUpdatePending || m_state == InStyleRecalc ||
         m_state == StyleClean || m_state == LayoutSubtreeChangeClean ||
         m_state == InPreLayout || m_state == LayoutClean ||
         m_state == CompositingClean || m_state == PaintInvalidationClean ||
         m_state == PrePaintClean || m_state == PaintClean ||
         m_state == Stopping;
}

inline bool DocumentLifecycle::stateAllowsLayoutInvalidation() const {
  return m_state != InPerformLayout && m_state != InCompositingUpdate &&
         m_state != InPaintInvalidation && m_state != InPrePaint &&
         m_state != InPaint;
}

}  // namespace blink

#endif
