blob: ae5877f352dcfa14bd9a919ca6254334e90910c2 [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/gmock/include/gmock/gmock.h"
#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/css/css_syntax_definition.h"
#include "third_party/blink/renderer/core/css/cssom/prepopulated_computed_style_property_map.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_object.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/paint_generated_image.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
namespace blink {
class TestPaintWorklet : public PaintWorklet {
public:
explicit TestPaintWorklet(LocalFrame* frame) : PaintWorklet(frame) {
ResetIsPaintOffThreadForTesting();
}
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::kNumGlobalScopesPerThread;
}
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);
const LayoutObject& layout_object =
static_cast<const LayoutObject&>(*observer);
float zoom = layout_object.StyleRef().EffectiveZoom();
StylePropertyMapReadOnly* style_map =
MakeGarbageCollected<PrepopulatedComputedStylePropertyMap>(
layout_object.GetDocument(), layout_object.StyleRef(),
definition->NativeInvalidationProperties(),
definition->CustomInvalidationProperties());
scoped_refptr<Image> image = PaintGeneratedImage::Create(
definition->Paint(container_size, zoom, style_map, nullptr,
1.0 /* device_scale_factor */),
container_size);
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;
// Please refer to CSSPaintImageGeneratorImpl::GetValidDocumentDefinition for
// the logic.
if (RuntimeEnabledFeatures::OffMainThreadCSSPaintEnabled()) {
EXPECT_TRUE(generator->GetValidDocumentDefinitionForTesting(definition));
} else {
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();
}
TEST_F(PaintWorkletTest, NativeAndCustomProperties) {
ScopedOffMainThreadCSSPaintForTest off_main_thread_css_paint(true);
Vector<CSSPropertyID> native_invalidation_properties = {
CSSPropertyID::kColor,
CSSPropertyID::kZoom,
CSSPropertyID::kTop,
};
Vector<String> custom_invalidation_properties = {
"--my-property",
"--another-property",
};
TestPaintWorklet* paint_worklet_to_test = GetTestPaintWorklet();
paint_worklet_to_test->RegisterMainThreadDocumentPaintDefinition(
"foo", native_invalidation_properties, custom_invalidation_properties,
Vector<CSSSyntaxDefinition>(), true);
CSSPaintImageGeneratorImpl* generator =
MakeGarbageCollected<CSSPaintImageGeneratorImpl>(paint_worklet_to_test,
"foo");
EXPECT_NE(generator, nullptr);
EXPECT_EQ(generator->NativeInvalidationProperties().size(), 3u);
EXPECT_EQ(generator->CustomInvalidationProperties().size(), 2u);
EXPECT_TRUE(generator->HasAlpha());
}
class MainOrOffThreadPaintWorkletTest
: public PageTestBase,
public ::testing::WithParamInterface<bool>,
private ScopedOffMainThreadCSSPaintForTest {
public:
MainOrOffThreadPaintWorkletTest()
: ScopedOffMainThreadCSSPaintForTest(GetParam()) {}
};
INSTANTIATE_TEST_SUITE_P(, MainOrOffThreadPaintWorkletTest, ::testing::Bool());
class MockObserver final : public CSSPaintImageGenerator::Observer {
public:
MOCK_METHOD0(PaintImageGeneratorReady, void());
};
TEST_P(MainOrOffThreadPaintWorkletTest, ConsistentGlobalScopeOnMainThread) {
PaintWorklet* paint_worklet_to_test =
PaintWorklet::From(*GetFrame().GetDocument()->domWindow());
paint_worklet_to_test->ResetIsPaintOffThreadForTesting();
MockObserver* observer = MakeGarbageCollected<MockObserver>();
CSSPaintImageGeneratorImpl* generator_foo =
MakeGarbageCollected<CSSPaintImageGeneratorImpl>(
observer, paint_worklet_to_test, "foo");
paint_worklet_to_test->AddPendingGenerator("foo", generator_foo);
CSSPaintImageGeneratorImpl* generator_bar =
MakeGarbageCollected<CSSPaintImageGeneratorImpl>(
observer, paint_worklet_to_test, "bar");
paint_worklet_to_test->AddPendingGenerator("bar", generator_bar);
// The generator should not fire unless it is the second registration
// for the main thread case
EXPECT_CALL(*observer, PaintImageGeneratorReady).Times(0);
Vector<Persistent<PaintWorkletGlobalScope>> global_scopes;
for (size_t i = 0; i < PaintWorklet::kNumGlobalScopesPerThread; ++i) {
paint_worklet_to_test->AddGlobalScopeForTesting();
global_scopes.push_back(
PaintWorkletGlobalScopeProxy::From(
paint_worklet_to_test->GetGlobalScopesForTesting()[i])
->global_scope());
}
String foo0 = R"JS(registerPaint('foo', class {
static get inputProperties() { return ['--foo0']; }
paint() {}
});)JS";
String foo1 = R"JS(registerPaint('foo', class {
static get inputProperties() { return ['--foo1']; }
paint() {}
});)JS";
String bar = R"JS(registerPaint('bar', class {
static get inputProperties() { return ['--bar']; }
paint() {}
});)JS";
global_scopes[0]->ScriptController()->Evaluate(
ScriptSourceCode(foo0), SanitizeScriptErrors::kDoNotSanitize);
EXPECT_TRUE(global_scopes[0]->FindDefinition("foo"));
EXPECT_TRUE(paint_worklet_to_test->GetDocumentDefinitionMap().at("foo"));
global_scopes[1]->ScriptController()->Evaluate(
ScriptSourceCode(foo1), SanitizeScriptErrors::kDoNotSanitize);
// foo0 and foo1 have the same name but different definitions, therefore
// this definition must become invalid.
EXPECT_FALSE(paint_worklet_to_test->GetDocumentDefinitionMap().at("foo"));
global_scopes[0]->ScriptController()->Evaluate(
ScriptSourceCode(bar), SanitizeScriptErrors::kDoNotSanitize);
EXPECT_TRUE(global_scopes[0]->FindDefinition("bar"));
EXPECT_TRUE(paint_worklet_to_test->GetDocumentDefinitionMap().at("bar"));
// When running in main-thread mode, the generator is now ready after this
// call. For off-thread, we are still waiting on the cross-thread
// registration.
if (!RuntimeEnabledFeatures::OffMainThreadCSSPaintEnabled())
EXPECT_CALL(*observer, PaintImageGeneratorReady).Times(1);
global_scopes[1]->ScriptController()->Evaluate(
ScriptSourceCode(bar), SanitizeScriptErrors::kDoNotSanitize);
EXPECT_TRUE(paint_worklet_to_test->GetDocumentDefinitionMap().at("bar"));
}
TEST_P(MainOrOffThreadPaintWorkletTest, AllGlobalScopesMustBeCreated) {
PaintWorklet* paint_worklet_to_test =
MakeGarbageCollected<PaintWorklet>(&GetFrame());
paint_worklet_to_test->ResetIsPaintOffThreadForTesting();
EXPECT_TRUE(paint_worklet_to_test->GetGlobalScopesForTesting().IsEmpty());
std::unique_ptr<PaintWorkletPaintDispatcher> dispatcher =
std::make_unique<PaintWorkletPaintDispatcher>();
Persistent<PaintWorkletProxyClient> proxy_client =
MakeGarbageCollected<PaintWorkletProxyClient>(
1, paint_worklet_to_test, dispatcher->GetWeakPtr(), nullptr);
paint_worklet_to_test->SetProxyClientForTesting(proxy_client);
while (paint_worklet_to_test->NeedsToCreateGlobalScopeForTesting()) {
paint_worklet_to_test->AddGlobalScopeForTesting();
}
if (RuntimeEnabledFeatures::OffMainThreadCSSPaintEnabled()) {
EXPECT_EQ(paint_worklet_to_test->GetGlobalScopesForTesting().size(),
2 * PaintWorklet::kNumGlobalScopesPerThread);
} else {
EXPECT_EQ(paint_worklet_to_test->GetGlobalScopesForTesting().size(),
PaintWorklet::kNumGlobalScopesPerThread);
}
}
TEST_F(PaintWorkletTest, ConsistentGlobalScopeCrossThread) {
ScopedOffMainThreadCSSPaintForTest off_main_thread_css_paint(true);
PaintWorklet* paint_worklet_to_test =
PaintWorklet::From(*GetFrame().GetDocument()->domWindow());
paint_worklet_to_test->ResetIsPaintOffThreadForTesting();
MockObserver* observer = MakeGarbageCollected<MockObserver>();
CSSPaintImageGeneratorImpl* generator_foo =
MakeGarbageCollected<CSSPaintImageGeneratorImpl>(
observer, paint_worklet_to_test, "foo");
paint_worklet_to_test->AddPendingGenerator("foo", generator_foo);
CSSPaintImageGeneratorImpl* generator_bar =
MakeGarbageCollected<CSSPaintImageGeneratorImpl>(
observer, paint_worklet_to_test, "bar");
paint_worklet_to_test->AddPendingGenerator("bar", generator_bar);
CSSPaintImageGeneratorImpl* generator_loo =
MakeGarbageCollected<CSSPaintImageGeneratorImpl>(
observer, paint_worklet_to_test, "loo");
paint_worklet_to_test->AddPendingGenerator("loo", generator_loo);
CSSPaintImageGeneratorImpl* generator_gar =
MakeGarbageCollected<CSSPaintImageGeneratorImpl>(
observer, paint_worklet_to_test, "gar");
paint_worklet_to_test->AddPendingGenerator("gar", generator_gar);
// None of the situations covered in this test should cause the generator to
// fire.
EXPECT_CALL(*observer, PaintImageGeneratorReady).Times(0);
Vector<Persistent<PaintWorkletGlobalScope>> global_scopes;
for (size_t i = 0; i < PaintWorklet::kNumGlobalScopesPerThread; ++i) {
paint_worklet_to_test->AddGlobalScopeForTesting();
global_scopes.push_back(
PaintWorkletGlobalScopeProxy::From(
paint_worklet_to_test->GetGlobalScopesForTesting()[i])
->global_scope());
}
String foo0 = R"JS(registerPaint('foo', class {
static get inputProperties() { return ['--foo0']; }
paint() {}
});)JS";
String foo1 = R"JS(registerPaint('foo', class {
static get inputProperties() { return ['--foo1']; }
paint() {}
});)JS";
String bar0 = R"JS(registerPaint('bar', class {
static get inputProperties() { return ['--bar0']; }
paint() {}
});)JS";
String loo0 = R"JS(registerPaint('loo', class {
static get inputProperties() { return ['--loo0']; }
paint() {}
});)JS";
String loo1 = R"JS(registerPaint('loo', class {
static get inputProperties() { return ['--loo1']; }
paint() {}
});)JS";
String gar0 = R"JS(registerPaint('gar', class {
static get inputProperties() { return ['--gar0']; }
paint() {}
});)JS";
// Definition invalidated before cross thread check
global_scopes[0]->ScriptController()->Evaluate(
ScriptSourceCode(foo0), SanitizeScriptErrors::kDoNotSanitize);
EXPECT_TRUE(global_scopes[0]->FindDefinition("foo"));
EXPECT_TRUE(paint_worklet_to_test->GetDocumentDefinitionMap().at("foo"));
global_scopes[1]->ScriptController()->Evaluate(
ScriptSourceCode(foo1), SanitizeScriptErrors::kDoNotSanitize);
EXPECT_FALSE(paint_worklet_to_test->GetDocumentDefinitionMap().at("foo"));
CSSPaintDefinition* definition = global_scopes[0]->FindDefinition("foo");
Vector<String> foo_custom_properties;
for (const auto& s : definition->CustomInvalidationProperties()) {
foo_custom_properties.push_back(s);
}
paint_worklet_to_test->RegisterMainThreadDocumentPaintDefinition(
"foo", definition->NativeInvalidationProperties(), foo_custom_properties,
definition->InputArgumentTypes(),
definition->GetPaintRenderingContext2DSettings()->alpha());
EXPECT_FALSE(paint_worklet_to_test->GetDocumentDefinitionMap().at("foo"));
// Definition invalidated by cross thread check
global_scopes[0]->ScriptController()->Evaluate(
ScriptSourceCode(bar0), SanitizeScriptErrors::kDoNotSanitize);
EXPECT_TRUE(global_scopes[0]->FindDefinition("bar"));
EXPECT_TRUE(paint_worklet_to_test->GetDocumentDefinitionMap().at("bar"));
global_scopes[1]->ScriptController()->Evaluate(
ScriptSourceCode(bar0), SanitizeScriptErrors::kDoNotSanitize);
EXPECT_TRUE(paint_worklet_to_test->GetDocumentDefinitionMap().at("bar"));
definition = global_scopes[0]->FindDefinition("bar");
// Manually change the custom properties
Vector<String> bar_custom_properties({"--bar1"});
paint_worklet_to_test->RegisterMainThreadDocumentPaintDefinition(
"bar", definition->NativeInvalidationProperties(), bar_custom_properties,
definition->InputArgumentTypes(),
definition->GetPaintRenderingContext2DSettings()->alpha());
// Although the main thread definitions were the same, the definition sent
// cross thread differed from the main thread definitions so it must become
// invalid.
EXPECT_FALSE(paint_worklet_to_test->GetDocumentDefinitionMap().at("bar"));
// Definition invalidated by second main thread call after cross thread check
global_scopes[0]->ScriptController()->Evaluate(
ScriptSourceCode(loo0), SanitizeScriptErrors::kDoNotSanitize);
EXPECT_TRUE(global_scopes[0]->FindDefinition("loo"));
EXPECT_TRUE(paint_worklet_to_test->GetDocumentDefinitionMap().at("loo"));
definition = global_scopes[0]->FindDefinition("loo");
Vector<String> loo_custom_properties;
for (const auto& s : definition->CustomInvalidationProperties()) {
loo_custom_properties.push_back(s);
}
paint_worklet_to_test->RegisterMainThreadDocumentPaintDefinition(
"loo", definition->NativeInvalidationProperties(), loo_custom_properties,
definition->InputArgumentTypes(),
definition->GetPaintRenderingContext2DSettings()->alpha());
EXPECT_TRUE(paint_worklet_to_test->GetDocumentDefinitionMap().at("loo"));
global_scopes[1]->ScriptController()->Evaluate(
ScriptSourceCode(loo1), SanitizeScriptErrors::kDoNotSanitize);
// Although the first main thread call and the cross thread definition are the
// same, the second main thread call differs so the definition must become
// invalid
EXPECT_FALSE(paint_worklet_to_test->GetDocumentDefinitionMap().at("loo"));
// Definition invalidated by cross thread check before second main thread call
global_scopes[0]->ScriptController()->Evaluate(
ScriptSourceCode(gar0), SanitizeScriptErrors::kDoNotSanitize);
EXPECT_TRUE(global_scopes[0]->FindDefinition("gar"));
EXPECT_TRUE(paint_worklet_to_test->GetDocumentDefinitionMap().at("gar"));
definition = global_scopes[0]->FindDefinition("gar");
// Manually change custom properties
Vector<String> gar_custom_properties({"--gar1"});
paint_worklet_to_test->RegisterMainThreadDocumentPaintDefinition(
"gar", definition->NativeInvalidationProperties(), gar_custom_properties,
definition->InputArgumentTypes(),
definition->GetPaintRenderingContext2DSettings()->alpha());
EXPECT_FALSE(paint_worklet_to_test->GetDocumentDefinitionMap().at("gar"));
global_scopes[1]->ScriptController()->Evaluate(
ScriptSourceCode(gar0), SanitizeScriptErrors::kDoNotSanitize);
// Although the main thread definitions were the same, the definition sent
// cross thread differed from the main thread definitions so it must stay
// invalid.
EXPECT_FALSE(paint_worklet_to_test->GetDocumentDefinitionMap().at("gar"));
}
TEST_F(PaintWorkletTest, GeneratorNotifiedAfterAllRegistrations) {
ScopedOffMainThreadCSSPaintForTest off_main_thread_css_paint(true);
PaintWorklet* paint_worklet_to_test =
PaintWorklet::From(*GetFrame().GetDocument()->domWindow());
paint_worklet_to_test->ResetIsPaintOffThreadForTesting();
MockObserver* observer = MakeGarbageCollected<MockObserver>();
CSSPaintImageGeneratorImpl* generator =
MakeGarbageCollected<CSSPaintImageGeneratorImpl>(
observer, paint_worklet_to_test, "foo");
paint_worklet_to_test->AddPendingGenerator("foo", generator);
// The generator should not fire until the cross thread check
EXPECT_CALL(*observer, PaintImageGeneratorReady).Times(0);
Vector<Persistent<PaintWorkletGlobalScope>> global_scopes;
for (size_t i = 0; i < PaintWorklet::kNumGlobalScopesPerThread; ++i) {
paint_worklet_to_test->AddGlobalScopeForTesting();
global_scopes.push_back(
PaintWorkletGlobalScopeProxy::From(
paint_worklet_to_test->GetGlobalScopesForTesting()[i])
->global_scope());
}
String foo = R"JS(registerPaint('foo', class {
static get inputProperties() { return ['--foo']; }
paint() {}
});)JS";
global_scopes[0]->ScriptController()->Evaluate(
ScriptSourceCode(foo), SanitizeScriptErrors::kDoNotSanitize);
EXPECT_TRUE(global_scopes[0]->FindDefinition("foo"));
EXPECT_TRUE(paint_worklet_to_test->GetDocumentDefinitionMap().at("foo"));
global_scopes[1]->ScriptController()->Evaluate(
ScriptSourceCode(foo), SanitizeScriptErrors::kDoNotSanitize);
EXPECT_TRUE(paint_worklet_to_test->GetDocumentDefinitionMap().at("foo"));
CSSPaintDefinition* definition = global_scopes[0]->FindDefinition("foo");
Vector<String> custom_properties;
for (const auto& s : definition->CustomInvalidationProperties()) {
custom_properties.push_back(s);
}
// The cross thread check should cause the generator to fire
EXPECT_CALL(*observer, PaintImageGeneratorReady).Times(1);
paint_worklet_to_test->RegisterMainThreadDocumentPaintDefinition(
"foo", definition->NativeInvalidationProperties(), custom_properties,
definition->InputArgumentTypes(),
definition->GetPaintRenderingContext2DSettings()->alpha());
EXPECT_TRUE(paint_worklet_to_test->GetDocumentDefinitionMap().at("foo"));
}
} // namespace blink