| // 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 |