blob: 2b4fb80a730aefddc80edfb33e615e955c3bf44e [file] [log] [blame]
// Copyright 2018 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/core/css/css_paint_value.h"
#include <memory>
#include "base/auto_reset.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/css/css_custom_ident_value.h"
#include "third_party/blink/renderer/core/css/css_syntax_definition.h"
#include "third_party/blink/renderer/core/css/mock_css_paint_image_generator.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/core/style/style_generated_image.h"
#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
#include "third_party/blink/renderer/platform/graphics/paint_generated_image.h"
#include "third_party/blink/renderer/platform/testing/histogram_tester.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
using testing::_;
using testing::NiceMock;
using testing::Return;
using testing::ReturnRef;
using testing::Values;
namespace blink {
namespace {
enum {
kCSSPaintAPIArguments = 1 << 0,
kOffMainThreadCSSPaint = 1 << 1,
};
class CSSPaintValueTest : public RenderingTest,
public ::testing::WithParamInterface<unsigned>,
private ScopedCSSPaintAPIArgumentsForTest,
private ScopedOffMainThreadCSSPaintForTest {
public:
CSSPaintValueTest()
: ScopedCSSPaintAPIArgumentsForTest(GetParam() & kCSSPaintAPIArguments),
ScopedOffMainThreadCSSPaintForTest(GetParam() &
kOffMainThreadCSSPaint) {}
// TODO(xidachen): a mock_generator is used in many tests in this file, put
// that in a Setup method.
};
INSTANTIATE_TEST_SUITE_P(All,
CSSPaintValueTest,
Values(0,
kCSSPaintAPIArguments,
kOffMainThreadCSSPaint,
kCSSPaintAPIArguments |
kOffMainThreadCSSPaint));
// CSSPaintImageGenerator requires that CSSPaintImageGeneratorCreateFunction be
// a static method. As such, it cannot access a class member and so instead we
// store a pointer to the overriding generator globally.
MockCSSPaintImageGenerator* g_override_generator = nullptr;
CSSPaintImageGenerator* ProvideOverrideGenerator(
const String&,
const Document&,
CSSPaintImageGenerator::Observer*) {
return g_override_generator;
}
} // namespace
TEST_P(CSSPaintValueTest, ReportingCompositedUMA) {
HistogramTester histogram_tester;
NiceMock<MockCSSPaintImageGenerator>* mock_generator =
MakeGarbageCollected<NiceMock<MockCSSPaintImageGenerator>>();
base::AutoReset<MockCSSPaintImageGenerator*> scoped_override_generator(
&g_override_generator, mock_generator);
base::AutoReset<CSSPaintImageGenerator::CSSPaintImageGeneratorCreateFunction>
scoped_create_function(
CSSPaintImageGenerator::GetCreateFunctionForTesting(),
ProvideOverrideGenerator);
const FloatSize target_size(100, 100);
SetBodyInnerHTML(R"HTML(<div id="target"></div>)HTML");
LayoutObject* target = GetLayoutObjectByElementId("target");
const ComputedStyle& style = *target->Style();
auto* ident = MakeGarbageCollected<CSSCustomIdentValue>("testpainter");
CSSPaintValue* paint_value = MakeGarbageCollected<CSSPaintValue>(ident, true);
// Mark the generator as ready - GetImage should succeed when
// OffMainThreadCSSPaint is enabled.
ON_CALL(*mock_generator, IsImageGeneratorReady()).WillByDefault(Return(true));
ON_CALL(*mock_generator, Paint(_, _, _, _))
.WillByDefault(Return(PaintGeneratedImage::Create(nullptr, target_size)));
ASSERT_TRUE(
paint_value->GetImage(*target, GetDocument(), style, target_size));
if (RuntimeEnabledFeatures::OffMainThreadCSSPaintEnabled()) {
histogram_tester.ExpectTotalCount("Blink.CSSPaintValue.PaintOffThread", 1u);
histogram_tester.ExpectUniqueSample("Blink.CSSPaintValue.PaintOffThread",
true, 1u);
} else {
histogram_tester.ExpectTotalCount("Blink.CSSPaintValue.PaintOffThread", 0u);
}
ASSERT_TRUE(
paint_value->GetImage(*target, GetDocument(), style, target_size));
if (RuntimeEnabledFeatures::OffMainThreadCSSPaintEnabled()) {
// Repaint, should not report histogram again.
histogram_tester.ExpectTotalCount("Blink.CSSPaintValue.PaintOffThread", 1u);
histogram_tester.ExpectUniqueSample("Blink.CSSPaintValue.PaintOffThread",
true, 1u);
} else {
histogram_tester.ExpectTotalCount("Blink.CSSPaintValue.PaintOffThread", 0u);
}
}
TEST_P(CSSPaintValueTest, ReportingNonCompositedUMA) {
HistogramTester histogram_tester;
NiceMock<MockCSSPaintImageGenerator>* mock_generator =
MakeGarbageCollected<NiceMock<MockCSSPaintImageGenerator>>();
mock_generator->AddNativeProperty();
base::AutoReset<MockCSSPaintImageGenerator*> scoped_override_generator(
&g_override_generator, mock_generator);
base::AutoReset<CSSPaintImageGenerator::CSSPaintImageGeneratorCreateFunction>
scoped_create_function(
CSSPaintImageGenerator::GetCreateFunctionForTesting(),
ProvideOverrideGenerator);
const FloatSize target_size(100, 100);
SetBodyInnerHTML(R"HTML(<div id="target"></div>)HTML");
LayoutObject* target = GetLayoutObjectByElementId("target");
auto style = ComputedStyle::Create();
auto* ident = MakeGarbageCollected<CSSCustomIdentValue>("testpainter");
CSSPaintValue* paint_value = MakeGarbageCollected<CSSPaintValue>(ident, true);
StyleGeneratedImage* style_image =
MakeGarbageCollected<StyleGeneratedImage>(*paint_value);
style->SetBorderImageSource(style_image);
ON_CALL(*mock_generator, IsImageGeneratorReady()).WillByDefault(Return(true));
EXPECT_CALL(*mock_generator, Paint(_, _, _, _))
.WillRepeatedly(
Return(PaintGeneratedImage::Create(nullptr, target_size)));
// The paint worklet is not composited, and falls back to the main thread
// paint.
ASSERT_TRUE(
paint_value->GetImage(*target, GetDocument(), *style, target_size));
if (RuntimeEnabledFeatures::OffMainThreadCSSPaintEnabled()) {
histogram_tester.ExpectTotalCount("Blink.CSSPaintValue.PaintOffThread", 1u);
histogram_tester.ExpectUniqueSample("Blink.CSSPaintValue.PaintOffThread",
false, 1u);
} else {
histogram_tester.ExpectTotalCount("Blink.CSSPaintValue.PaintOffThread", 0u);
}
// Repaint, should not report histogram again.
ASSERT_TRUE(
paint_value->GetImage(*target, GetDocument(), *style, target_size));
if (RuntimeEnabledFeatures::OffMainThreadCSSPaintEnabled()) {
histogram_tester.ExpectTotalCount("Blink.CSSPaintValue.PaintOffThread", 1u);
histogram_tester.ExpectUniqueSample("Blink.CSSPaintValue.PaintOffThread",
false, 1u);
} else {
histogram_tester.ExpectTotalCount("Blink.CSSPaintValue.PaintOffThread", 0u);
}
}
TEST_P(CSSPaintValueTest, DelayPaintUntilGeneratorReady) {
NiceMock<MockCSSPaintImageGenerator>* mock_generator =
MakeGarbageCollected<NiceMock<MockCSSPaintImageGenerator>>();
base::AutoReset<MockCSSPaintImageGenerator*> scoped_override_generator(
&g_override_generator, mock_generator);
base::AutoReset<CSSPaintImageGenerator::CSSPaintImageGeneratorCreateFunction>
scoped_create_function(
CSSPaintImageGenerator::GetCreateFunctionForTesting(),
ProvideOverrideGenerator);
const FloatSize target_size(100, 100);
SetBodyInnerHTML(R"HTML(
<div id="target"></div>
)HTML");
LayoutObject* target = GetLayoutObjectByElementId("target");
const ComputedStyle& style = *target->Style();
auto* ident = MakeGarbageCollected<CSSCustomIdentValue>("testpainter");
CSSPaintValue* paint_value = MakeGarbageCollected<CSSPaintValue>(ident, true);
// Initially the generator is not ready, so GetImage should fail (and no paint
// should happen).
EXPECT_CALL(*mock_generator, Paint(_, _, _, _)).Times(0);
EXPECT_FALSE(
paint_value->GetImage(*target, GetDocument(), style, target_size));
// Now mark the generator as ready - GetImage should then succeed.
ON_CALL(*mock_generator, IsImageGeneratorReady()).WillByDefault(Return(true));
// In off-thread CSS Paint, the actual paint call is deferred and so will
// never happen.
if (!RuntimeEnabledFeatures::OffMainThreadCSSPaintEnabled()) {
EXPECT_CALL(*mock_generator, Paint(_, _, _, _))
.WillRepeatedly(
Return(PaintGeneratedImage::Create(nullptr, target_size)));
}
EXPECT_TRUE(
paint_value->GetImage(*target, GetDocument(), style, target_size));
}
// Regression test for crbug.com/998439. The problem is that GetImage is called
// on a new document. This test simulates the situation by having two different
// documents and call GetImage on different ones.
TEST_P(CSSPaintValueTest, GetImageCalledOnMultipleDocuments) {
const FloatSize target_size(100, 100);
SetBodyInnerHTML(R"HTML(<div id="target"></div>)HTML");
LayoutObject* target = GetLayoutObjectByElementId("target");
const ComputedStyle& style = *target->Style();
auto* ident = MakeGarbageCollected<CSSCustomIdentValue>("testpainter");
CSSPaintValue* paint_value = MakeGarbageCollected<CSSPaintValue>(ident, true);
EXPECT_EQ(paint_value->NumberOfGeneratorsForTesting(), 0u);
paint_value->GetImage(*target, GetDocument(), style, target_size);
// A new generator should be created if there is no generator exists.
EXPECT_EQ(paint_value->NumberOfGeneratorsForTesting(), 1u);
auto new_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
// Call GetImage on a new Document should not crash.
paint_value->GetImage(*target, new_page_holder->GetDocument(), style,
target_size);
EXPECT_EQ(paint_value->NumberOfGeneratorsForTesting(), 2u);
}
TEST_P(CSSPaintValueTest, NativeInvalidationPropertiesWithNoGenerator) {
SetBodyInnerHTML(R"HTML(<div id="target"></div>)HTML");
auto* ident = MakeGarbageCollected<CSSCustomIdentValue>("testpainter");
CSSPaintValue* paint_value = MakeGarbageCollected<CSSPaintValue>(ident, true);
EXPECT_EQ(paint_value->NumberOfGeneratorsForTesting(), 0u);
// There is no generator, so returning a nullptr.
EXPECT_EQ(paint_value->NativeInvalidationProperties(GetDocument()), nullptr);
}
TEST_P(CSSPaintValueTest, CustomInvalidationPropertiesWithNoGenerator) {
SetBodyInnerHTML(R"HTML(<div id="target"></div>)HTML");
auto* ident = MakeGarbageCollected<CSSCustomIdentValue>("testpainter");
CSSPaintValue* paint_value = MakeGarbageCollected<CSSPaintValue>(ident, true);
EXPECT_EQ(paint_value->NumberOfGeneratorsForTesting(), 0u);
// There is no generator, so returning a nullptr.
EXPECT_EQ(paint_value->CustomInvalidationProperties(GetDocument()), nullptr);
}
TEST_P(CSSPaintValueTest, PrintingMustFallbackToMainThread) {
if (!RuntimeEnabledFeatures::OffMainThreadCSSPaintEnabled())
return;
NiceMock<MockCSSPaintImageGenerator>* mock_generator =
MakeGarbageCollected<NiceMock<MockCSSPaintImageGenerator>>();
base::AutoReset<MockCSSPaintImageGenerator*> scoped_override_generator(
&g_override_generator, mock_generator);
base::AutoReset<CSSPaintImageGenerator::CSSPaintImageGeneratorCreateFunction>
scoped_create_function(
CSSPaintImageGenerator::GetCreateFunctionForTesting(),
ProvideOverrideGenerator);
const FloatSize target_size(100, 100);
SetBodyInnerHTML(R"HTML(
<div id="target"></div>
)HTML");
LayoutObject* target = GetLayoutObjectByElementId("target");
const ComputedStyle& style = *target->Style();
auto* ident = MakeGarbageCollected<CSSCustomIdentValue>("testpainter");
CSSPaintValue* paint_value = MakeGarbageCollected<CSSPaintValue>(ident, true);
ON_CALL(*mock_generator, IsImageGeneratorReady()).WillByDefault(Return(true));
// This PW can be composited, so we should only fall back to main once, in
// the case where we are printing.
EXPECT_CALL(*mock_generator, Paint(_, _, _, _))
.Times(1)
.WillOnce(Return(PaintGeneratedImage::Create(nullptr, target_size)));
ASSERT_TRUE(
paint_value->GetImage(*target, GetDocument(), style, target_size));
// Start printing; our paint should run on the main thread (and thus call
// Paint).
GetDocument().SetPrinting(Document::kPrinting);
ASSERT_TRUE(
paint_value->GetImage(*target, GetDocument(), style, target_size));
// Stop printing; we should return to the compositor.
GetDocument().SetPrinting(Document::kNotPrinting);
ASSERT_TRUE(
paint_value->GetImage(*target, GetDocument(), style, target_size));
}
// Regression test for https://crbug.com/835589.
TEST_P(CSSPaintValueTest, DoNotPaintForLink) {
SetBodyInnerHTML(R"HTML(
<style>
a {
background-image: paint(linkpainter);
width: 100px;
height: 100px;
}
</style>
<a href="http://www.example.com" id="target"></a>
)HTML");
LayoutObject* target = GetLayoutObjectByElementId("target");
const ComputedStyle& style = *target->Style();
ASSERT_NE(style.InsideLink(), EInsideLink::kNotInsideLink);
auto* ident = MakeGarbageCollected<CSSCustomIdentValue>("linkpainter");
CSSPaintValue* paint_value = MakeGarbageCollected<CSSPaintValue>(ident, true);
EXPECT_FALSE(paint_value->GetImage(*target, GetDocument(), style,
FloatSize(100, 100)));
}
// Regression test for https://crbug.com/835589.
TEST_P(CSSPaintValueTest, DoNotPaintWhenAncestorHasLink) {
SetBodyInnerHTML(R"HTML(
<style>
a {
width: 200px;
height: 200px;
}
b {
background-image: paint(linkpainter);
width: 100px;
height: 100px;
}
</style>
<a href="http://www.example.com" id="ancestor">
<b id="target"></b>
</a>
)HTML");
LayoutObject* target = GetLayoutObjectByElementId("target");
const ComputedStyle& style = *target->Style();
ASSERT_NE(style.InsideLink(), EInsideLink::kNotInsideLink);
auto* ident = MakeGarbageCollected<CSSCustomIdentValue>("linkpainter");
CSSPaintValue* paint_value = MakeGarbageCollected<CSSPaintValue>(ident, true);
EXPECT_FALSE(paint_value->GetImage(*target, GetDocument(), style,
FloatSize(100, 100)));
}
TEST_P(CSSPaintValueTest, BuildInputArgumentValuesNotCrash) {
auto* ident = MakeGarbageCollected<CSSCustomIdentValue>("testpainter");
CSSPaintValue* paint_value = MakeGarbageCollected<CSSPaintValue>(ident, true);
ASSERT_EQ(paint_value->GetParsedInputArgumentsForTesting(), nullptr);
Vector<std::unique_ptr<CrossThreadStyleValue>> cross_thread_input_arguments;
paint_value->BuildInputArgumentValuesForTesting(cross_thread_input_arguments);
EXPECT_EQ(cross_thread_input_arguments.size(), 0u);
}
} // namespace blink