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

#include "third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope.h"

#include "base/synchronization/waitable_event.h"
#include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/inspector/worker_devtools_params.h"
#include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
#include "third_party/blink/renderer/core/script/classic_script.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/core/workers/global_scope_creation_params.h"
#include "third_party/blink/renderer/core/workers/worker_reporting_proxy.h"
#include "third_party/blink/renderer/modules/csspaint/css_paint_definition.h"
#include "third_party/blink/renderer/modules/csspaint/document_paint_definition.h"
#include "third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client.h"
#include "third_party/blink/renderer/modules/worklet/animation_and_paint_worklet_thread.h"
#include "third_party/blink/renderer/modules/worklet/worklet_thread_test_common.h"
#include "third_party/blink/renderer/platform/graphics/paint_worklet_paint_dispatcher.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"

namespace blink {

// TODO(smcgruer): Extract a common base class between this and
// AnimationWorkletGlobalScope.
class PaintWorkletGlobalScopeTest : public PageTestBase {
 public:
  PaintWorkletGlobalScopeTest() = default;

  void SetUp() override {
    PageTestBase::SetUp(gfx::Size());
    NavigateTo(KURL("https://example.com/"));
    // This test only needs the proxy client set to avoid calling
    // PaintWorkletProxyClient::Create, but it doesn't need the dispatcher/etc.
    proxy_client_ = MakeGarbageCollected<PaintWorkletProxyClient>(
        1, nullptr, GetFrame().GetTaskRunner(TaskType::kInternalDefault),
        nullptr, nullptr);
    reporting_proxy_ = std::make_unique<WorkerReportingProxy>();
  }

  using TestCallback =
      void (PaintWorkletGlobalScopeTest::*)(WorkerThread*,
                                            PaintWorkletProxyClient*,
                                            base::WaitableEvent*);

  // Create a new paint worklet and run the callback task on it. Terminate the
  // worklet once the task completion is signaled.
  void RunTestOnWorkletThread(TestCallback callback) {
    std::unique_ptr<WorkerThread> worklet =
        CreateThreadAndProvidePaintWorkletProxyClient(
            &GetDocument(), reporting_proxy_.get(), proxy_client_);
    base::WaitableEvent waitable_event;
    PostCrossThreadTask(
        *worklet->GetTaskRunner(TaskType::kInternalTest), FROM_HERE,
        CrossThreadBindOnce(
            callback, CrossThreadUnretained(this),
            CrossThreadUnretained(worklet.get()),
            CrossThreadPersistent<PaintWorkletProxyClient>(proxy_client_),
            CrossThreadUnretained(&waitable_event)));
    waitable_event.Wait();
    waitable_event.Reset();

    worklet->Terminate();
    worklet->WaitForShutdownForTesting();
  }

  void RunBasicParsingTestOnWorklet(WorkerThread* thread,
                                    PaintWorkletProxyClient* proxy_client,
                                    base::WaitableEvent* waitable_event) {
    ASSERT_TRUE(thread->IsCurrentThread());
    CrossThreadPersistent<PaintWorkletGlobalScope> global_scope =
        WrapCrossThreadPersistent(
            To<PaintWorkletGlobalScope>(thread->GlobalScope()));

    {
      // registerPaint() with a valid class definition should define a painter.
      String source_code =
          R"JS(
            registerPaint('test', class {
              constructor () {}
              paint (ctx, size) {}
            });
          )JS";
      ClassicScript::CreateUnspecifiedScript(source_code)
          ->RunScriptOnScriptState(
              global_scope->ScriptController()->GetScriptState());
      CSSPaintDefinition* definition = global_scope->FindDefinition("test");
      ASSERT_TRUE(definition);
    }

    {
      // registerPaint() with a null class definition should fail to define a
      // painter.
      String source_code = "registerPaint('null', null);";
      ClassicScript::CreateUnspecifiedScript(source_code)
          ->RunScriptOnScriptState(
              global_scope->ScriptController()->GetScriptState());
      EXPECT_FALSE(global_scope->FindDefinition("null"));
    }

    EXPECT_FALSE(global_scope->FindDefinition("non-existent"));

    waitable_event->Signal();
  }

 private:
  Persistent<PaintWorkletProxyClient> proxy_client_;
  std::unique_ptr<WorkerReportingProxy> reporting_proxy_;
};

TEST_F(PaintWorkletGlobalScopeTest, BasicParsing) {
  RunTestOnWorkletThread(
      &PaintWorkletGlobalScopeTest::RunBasicParsingTestOnWorklet);
}

}  // namespace blink
