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

#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_CSSPAINT_PAINT_WORKLET_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_CSSPAINT_PAINT_WORKLET_H_

#include <memory>

#include "third_party/blink/renderer/core/css/css_syntax_definition.h"
#include "third_party/blink/renderer/core/workers/worklet.h"
#include "third_party/blink/renderer/modules/csspaint/document_paint_definition.h"
#include "third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_proxy.h"
#include "third_party/blink/renderer/modules/csspaint/paint_worklet_pending_generator_registry.h"
#include "third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.h"
#include "third_party/blink/renderer/modules/modules_export.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"

namespace blink {

class CSSPaintImageGeneratorImpl;

// Manages a paint worklet:
// https://drafts.css-houdini.org/css-paint-api/#dom-css-paintworklet
class MODULES_EXPORT PaintWorklet : public Worklet,
                                    public Supplement<LocalDOMWindow> {
 public:
  static const char kSupplementName[];

  // At this moment, paint worklet allows at most two global scopes at any time.
  static const wtf_size_t kNumGlobalScopesPerThread;
  static PaintWorklet* From(LocalDOMWindow&);

  explicit PaintWorklet(LocalDOMWindow&);

  PaintWorklet(const PaintWorklet&) = delete;
  PaintWorklet& operator=(const PaintWorklet&) = delete;

  ~PaintWorklet() override;

  void AddPendingGenerator(const String& name, CSSPaintImageGeneratorImpl*);
  // The |container_size| is without subpixel snapping.
  scoped_refptr<Image> Paint(const String& name,
                             const ImageResourceObserver&,
                             const gfx::SizeF& container_size,
                             const GCedCSSStyleValueVector*);

  int WorkletId() const { return worklet_id_; }
  bool IsOffMainThread() const { return is_paint_off_thread_; }

  void Trace(Visitor*) const override;

  // The DocumentDefinitionMap tracks definitions registered via
  // registerProperty; definitions are only considered valid once all global
  // scopes have registered the same definition for the same thread.
  typedef HashMap<String, std::unique_ptr<DocumentPaintDefinition>>
      DocumentDefinitionMap;
  DocumentDefinitionMap& GetDocumentDefinitionMap() {
    return document_definition_map_;
  }

  // Used for main-thread CSS Paint. Registers a definition for a given painter,
  // ensuring that the same CSSPaintDefinition is registered on all global
  // scopes.
  void RegisterCSSPaintDefinition(const String& name,
                                  CSSPaintDefinition*,
                                  ExceptionState&);

  // Used for off-thread CSS Paint. In this mode we are not responsible for
  // tracking whether a definition is valid - this method should only be called
  // once all global scopes have registered the same |DocumentPaintDefinition|
  // for the same |name|.
  void RegisterMainThreadDocumentPaintDefinition(
      const String& name,
      Vector<CSSPropertyID> native_properties,
      Vector<AtomicString> custom_properties,
      Vector<CSSSyntaxDefinition> input_argument_types,
      double alpha);

  HeapVector<Member<WorkletGlobalScopeProxy>>& GetGlobalScopesForTesting() {
    return proxies_;
  }

  void AddGlobalScopeForTesting() { proxies_.push_back(CreateGlobalScope()); }

  bool NeedsToCreateGlobalScopeForTesting() {
    return NeedsToCreateGlobalScope();
  }

  void SetProxyClientForTesting(PaintWorkletProxyClient* proxy_client) {
    proxy_client_ = proxy_client;
  }

  void ResetIsPaintOffThreadForTesting(bool is_off_thread = true);

 protected:
  // Since paint worklet has more than one global scope, we MUST override this
  // function and provide our own selection logic.
  wtf_size_t SelectGlobalScope() final;
  wtf_size_t GetActiveGlobalScopeForTesting() { return active_global_scope_; }

 private:
  friend class PaintWorkletTest;

  // Implements Worklet.
  bool NeedsToCreateGlobalScope() final;
  WorkletGlobalScopeProxy* CreateGlobalScope() final;

  // This function calculates the number of paints to use before switching
  // global scopes.
  virtual int GetPaintsBeforeSwitching();
  // This function calculates the next global scope to switch to.
  virtual wtf_size_t SelectNewGlobalScope();

  Member<PaintWorkletPendingGeneratorRegistry> pending_generator_registry_;

  // Used for both main and off-thread CSS Paint.
  // For the main thread, this map tracks the definitions created on the main
  // thread, and ensures that all global scopes have the same definition.
  //
  // For the off thread case, both the worklet and main thread have this map.
  // The worklet version is responsible for verifying that all global scopes
  // have the same definition, and the main thread version relies on that.
  //
  // The value of an entry being nullptr means that it is an invalid definition.
  DocumentDefinitionMap document_definition_map_;

  // The last document paint frame a paint worklet painted on. This is used to
  // tell when we begin painting on a new frame.
  size_t active_frame_count_ = 0u;
  // The current global scope being used for painting.
  wtf_size_t active_global_scope_ = 0u;
  // The number of paint calls remaining before Paint will select a new global
  // scope. SelectGlobalScope resets this at the beginning of each frame.
  int paints_before_switching_global_scope_;

  // An atomic sequence number to ensure that it is unique for each paint
  // worklet. This id is integrated in the PaintWorkletInput which will be used
  // in PaintWorkletPaintDispatcher::Paint, to identify the right painter, to
  // paint the image.
  int worklet_id_;

  // The proxy client associated with this PaintWorklet. We keep a reference in
  // to ensure that all global scopes get the same proxy client.
  Member<PaintWorkletProxyClient> proxy_client_;

  // When running layout test, paint worklet has to be on the main thread
  // because "enable-threaded-compositing" is off by default. However, some unit
  // tests may be testing the functionality of the APIs when the paint worklet
  // is off the main thread.
  bool is_paint_off_thread_;
};

}  // namespace blink

#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_CSSPAINT_PAINT_WORKLET_H_
