blob: 3d6790bba792a0303855b1120efe4e7603488ed3 [file] [log] [blame]
// Copyright 2016 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.
#include "third_party/blink/renderer/modules/csspaint/paint_worklet.h"
#include <memory>
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/bindings/core/v8/script_source_code.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_gc_controller.h"
#include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/modules/csspaint/css_paint_definition.h"
#include "third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope.h"
#include "third_party/blink/renderer/modules/csspaint/paint_worklet_global_scope_proxy.h"
#include "third_party/blink/renderer/platform/graphics/image.h"
namespace blink {
class TestPaintWorklet : public PaintWorklet {
public:
explicit TestPaintWorklet(LocalFrame* frame) : PaintWorklet(frame) {}
void SetPaintsToSwitch(int num) { paints_to_switch_ = num; }
int GetPaintsBeforeSwitching() override { return paints_to_switch_; }
// We always switch to another global scope so that we can tell how often it
// was switched in the test.
wtf_size_t SelectNewGlobalScope() override {
return (GetActiveGlobalScopeForTesting() + 1) %
PaintWorklet::kNumGlobalScopes;
}
size_t GetActiveGlobalScope() { return GetActiveGlobalScopeForTesting(); }
private:
int paints_to_switch_;
};
class PaintWorkletTest : public PageTestBase {
public:
void SetUp() override {
PageTestBase::SetUp(IntSize());
test_paint_worklet_ = MakeGarbageCollected<TestPaintWorklet>(
GetDocument().domWindow()->GetFrame());
proxy_ = test_paint_worklet_->CreateGlobalScope();
}
TestPaintWorklet* GetTestPaintWorklet() { return test_paint_worklet_.Get(); }
size_t SelectGlobalScope(TestPaintWorklet* paint_worklet) {
return paint_worklet->SelectGlobalScope();
}
PaintWorkletGlobalScopeProxy* GetProxy() {
return PaintWorkletGlobalScopeProxy::From(proxy_.Get());
}
ImageResourceObserver* GetImageResourceObserver() {
return GetDocument().domWindow()->GetFrame()->ContentLayoutObject();
}
// Helper function used in GlobalScopeSelection test.
void ExpectSwitchGlobalScope(bool expect_switch_within_frame,
size_t num_paint_calls,
int paint_cnt_to_switch,
size_t expected_num_paints_before_switch,
TestPaintWorklet* paint_worklet_to_test) {
paint_worklet_to_test->GetFrame()->View()->UpdateAllLifecyclePhases(
DocumentLifecycle::LifecycleUpdateReason::kTest);
paint_worklet_to_test->SetPaintsToSwitch(paint_cnt_to_switch);
size_t previously_selected_global_scope =
paint_worklet_to_test->GetActiveGlobalScope();
size_t global_scope_switch_count = 0u;
// How many paint calls are there before we switch to another global scope.
// Because the first paint call in each frame doesn't count as switching,
// a result of 0 means there is not switching in that frame.
size_t num_paints_before_switch = 0u;
for (size_t j = 0; j < num_paint_calls; j++) {
size_t selected_global_scope = SelectGlobalScope(paint_worklet_to_test);
if (j == 0) {
EXPECT_NE(selected_global_scope, previously_selected_global_scope);
} else if (selected_global_scope != previously_selected_global_scope) {
num_paints_before_switch = j + 1;
global_scope_switch_count++;
}
previously_selected_global_scope = selected_global_scope;
}
EXPECT_LT(global_scope_switch_count, 2u);
EXPECT_EQ(num_paints_before_switch, expected_num_paints_before_switch);
}
void Terminate() {
proxy_->TerminateWorkletGlobalScope();
proxy_ = nullptr;
}
private:
Persistent<WorkletGlobalScopeProxy> proxy_;
Persistent<TestPaintWorklet> test_paint_worklet_;
};
// This is a crash test for crbug.com/803026. At some point, we shipped the
// CSSPaintAPI without shipping the CSSPaintAPIArguments, the result of it is
// that the |paint_arguments| in the CSSPaintDefinition::Paint() becomes
// nullptr and the renderer crashes. This is a regression test to ensure that
// we will never crash.
TEST_F(PaintWorkletTest, PaintWithNullPaintArguments) {
PaintWorkletGlobalScope* global_scope = GetProxy()->global_scope();
global_scope->ScriptController()->Evaluate(
ScriptSourceCode("registerPaint('foo', class { paint() { } });"),
SanitizeScriptErrors::kDoNotSanitize);
CSSPaintDefinition* definition = global_scope->FindDefinition("foo");
ASSERT_TRUE(definition);
ImageResourceObserver* observer = GetImageResourceObserver();
ASSERT_TRUE(observer);
const FloatSize container_size(100, 100);
scoped_refptr<Image> image =
definition->Paint(*observer, container_size, nullptr);
EXPECT_NE(image, nullptr);
}
// In this test, we have only one global scope, which means registerPaint is
// called only once, and hence we have only one document paint definition
// registered. In the real world, this document paint definition should not be
// used to paint until we see a second one being registed with the same name.
TEST_F(PaintWorkletTest, SinglyRegisteredDocumentDefinitionNotUsed) {
PaintWorkletGlobalScope* global_scope = GetProxy()->global_scope();
global_scope->ScriptController()->Evaluate(
ScriptSourceCode("registerPaint('foo', class { paint() { } });"),
SanitizeScriptErrors::kDoNotSanitize);
CSSPaintImageGeneratorImpl* generator =
static_cast<CSSPaintImageGeneratorImpl*>(
CSSPaintImageGeneratorImpl::Create("foo", GetDocument(), nullptr));
EXPECT_TRUE(generator);
EXPECT_EQ(generator->GetRegisteredDefinitionCountForTesting(), 1u);
DocumentPaintDefinition* definition;
EXPECT_FALSE(generator->GetValidDocumentDefinitionForTesting(definition));
EXPECT_FALSE(definition);
}
// In this test, we set a list of "paints_to_switch" numbers, and in each frame,
// we switch to a new global scope when the number of paint calls is >= the
// corresponding number.
TEST_F(PaintWorkletTest, GlobalScopeSelection) {
TestPaintWorklet* paint_worklet_to_test = GetTestPaintWorklet();
ExpectSwitchGlobalScope(false, 5, 1, 0, paint_worklet_to_test);
ExpectSwitchGlobalScope(true, 15, 10, 10, paint_worklet_to_test);
// In the last one where |paints_to_switch| is 20, there is no switching after
// the first paint call.
ExpectSwitchGlobalScope(false, 10, 20, 0, paint_worklet_to_test);
// Delete the page & associated objects.
Terminate();
}
} // namespace blink