blob: f0697587f2b64e24481f00d72f8001310b9f7cf4 [file] [log] [blame]
// Copyright 2022 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/core/css/style_image_cache.h"
#include "third_party/blink/renderer/core/css/style_engine.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/loader/empty_clients.h"
#include "third_party/blink/renderer/core/style/style_fetched_image.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/platform/heap/thread_state.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
namespace blink {
namespace {
constexpr char kTestResourceFilename[] = "background_image.png";
constexpr char kTestResourceMimeType[] = "image/png";
} // namespace
class StyleImageCacheTest : public PageTestBase {
protected:
void SetUp() override {
PageTestBase::SetUp();
GetDocument().SetBaseURLOverride(KURL("http://test.com"));
}
const HeapHashMap<String, WeakMember<ImageResourceContent>>&
FetchedImageMap() {
return GetDocument().GetStyleEngine().style_image_cache_.fetched_image_map_;
}
};
TEST_F(StyleImageCacheTest, DuplicateBackgroundImageURLs) {
SetBodyInnerHTML(R"HTML(
<style>
.rule1 { background-image: url(url.png) }
.rule2 { background-image: url(url.png) }
</style>
<div id="target"></div>
)HTML");
Element* target = GetDocument().getElementById(AtomicString("target"));
ASSERT_TRUE(target);
ASSERT_FALSE(target->ComputedStyleRef().BackgroundLayers().GetImage());
target->setAttribute(html_names::kClassAttr, AtomicString("rule1"));
UpdateAllLifecyclePhasesForTest();
StyleImage* rule1_image =
target->ComputedStyleRef().BackgroundLayers().GetImage();
EXPECT_TRUE(rule1_image);
target->setAttribute(html_names::kClassAttr, AtomicString("rule2"));
UpdateAllLifecyclePhasesForTest();
StyleImage* rule2_image =
target->ComputedStyleRef().BackgroundLayers().GetImage();
EXPECT_EQ(*rule1_image, *rule2_image);
}
TEST_F(StyleImageCacheTest, DifferingFragmentsBackgroundImageURLs) {
SetBodyInnerHTML(R"HTML(
<style>
.rule1 { background-image: url(url.svg#a) }
.rule2 { background-image: url(url.svg#b) }
</style>
<div id="target"></div>
)HTML");
Element* target = GetDocument().getElementById(AtomicString("target"));
ASSERT_TRUE(target);
ASSERT_FALSE(target->ComputedStyleRef().BackgroundLayers().GetImage());
target->setAttribute(html_names::kClassAttr, AtomicString("rule1"));
UpdateAllLifecyclePhasesForTest();
StyleImage* rule1_image =
target->ComputedStyleRef().BackgroundLayers().GetImage();
EXPECT_TRUE(rule1_image);
target->setAttribute(html_names::kClassAttr, AtomicString("rule2"));
UpdateAllLifecyclePhasesForTest();
StyleImage* rule2_image =
target->ComputedStyleRef().BackgroundLayers().GetImage();
EXPECT_NE(*rule1_image, *rule2_image);
EXPECT_EQ(rule1_image->CachedImage(), rule2_image->CachedImage());
}
TEST_F(StyleImageCacheTest, CustomPropertyURL) {
SetBodyInnerHTML(R"HTML(
<style>
:root { --bg: url(url.png) }
#target { background-image: var(--bg) }
.green { background-color: green }
</style>
<div id="target"></div>
)HTML");
Element* target = GetDocument().getElementById(AtomicString("target"));
StyleImage* initial_image =
target->ComputedStyleRef().BackgroundLayers().GetImage();
EXPECT_TRUE(initial_image);
target->setAttribute(html_names::kClassAttr, AtomicString("green"));
UpdateAllLifecyclePhasesForTest();
StyleImage* image_after_recalc =
target->ComputedStyleRef().BackgroundLayers().GetImage();
EXPECT_EQ(*initial_image, *image_after_recalc);
}
TEST_F(StyleImageCacheTest, ComputedValueRelativePath) {
SetBodyInnerHTML(R"HTML(
<style>
#target1 { background-image: url(http://test.com/url.png) }
#target2 { background-image: url(url.png) }
</style>
<div id="target1"></div>
<div id="target2"></div>
)HTML");
Element* target1 = GetDocument().getElementById(AtomicString("target1"));
Element* target2 = GetDocument().getElementById(AtomicString("target2"));
// Resolves to the same absolute url. Can share the underlying
// ImageResourceContent since the computed value is the absolute url.
EXPECT_EQ(*target1->ComputedStyleRef().BackgroundLayers().GetImage(),
*target2->ComputedStyleRef().BackgroundLayers().GetImage());
const CSSProperty& property =
CSSProperty::Get(CSSPropertyID::kBackgroundImage);
EXPECT_EQ(property
.CSSValueFromComputedStyle(target1->ComputedStyleRef(), nullptr,
false, CSSValuePhase::kComputedValue)
->CssText(),
"url(\"http://test.com/url.png\")");
EXPECT_EQ(property
.CSSValueFromComputedStyle(target2->ComputedStyleRef(), nullptr,
false, CSSValuePhase::kComputedValue)
->CssText(),
"url(\"http://test.com/url.png\")");
}
TEST_F(StyleImageCacheTest, WeakReferenceGC) {
SetBodyInnerHTML(R"HTML(
<style id="sheet">
#target1 { background-image: url(url.png) }
#target2 { background-image: url(url2.png) }
</style>
<div id="target1"></div>
<div id="target2"></div>
)HTML");
UpdateAllLifecyclePhasesForTest();
EXPECT_TRUE(FetchedImageMap().Contains("http://test.com/url.png"));
EXPECT_TRUE(FetchedImageMap().Contains("http://test.com/url2.png"));
EXPECT_EQ(FetchedImageMap().size(), 2u);
Element* sheet = GetDocument().getElementById(AtomicString("sheet"));
ASSERT_TRUE(sheet);
sheet->remove();
UpdateAllLifecyclePhasesForTest();
ThreadState::Current()->CollectAllGarbageForTesting();
// After the sheet has been removed, the lifecycle update and garbage
// collection have been run, the weak references in the cache should have been
// collected.
EXPECT_FALSE(FetchedImageMap().Contains("http://test.com/url.png"));
EXPECT_FALSE(FetchedImageMap().Contains("http://test.com/url2.png"));
EXPECT_EQ(FetchedImageMap().size(), 0u);
}
class StyleImageCacheFrameClientTest : public EmptyLocalFrameClient {
public:
std::unique_ptr<URLLoader> CreateURLLoaderForTesting() override {
return URLLoaderMockFactory::GetSingletonInstance()->CreateURLLoader();
}
};
class StyleImageCacheWithLoadingTest : public StyleImageCacheTest {
public:
StyleImageCacheWithLoadingTest() = default;
~StyleImageCacheWithLoadingTest() override {
url_test_helpers::UnregisterAllURLsAndClearMemoryCache();
}
protected:
void SetUp() override {
auto setting_overrider = [](Settings& settings) {
settings.SetLoadsImagesAutomatically(true);
};
PageTestBase::SetupPageWithClients(
nullptr, MakeGarbageCollected<StyleImageCacheFrameClientTest>(),
setting_overrider);
}
};
TEST_F(StyleImageCacheWithLoadingTest, DuplicateBackgroundImageURLs) {
SetBodyInnerHTML(R"HTML(
<style>
.rule1 { background-image: url(http://test.com/background_image.png) }
.rule2 { background-image: url(http://test.com/background_image.png) }
</style>
<div id="target"></div>
)HTML");
url_test_helpers::RegisterMockedURLLoad(
url_test_helpers::ToKURL("http://test.com/background_image.png"),
test::CoreTestDataPath(kTestResourceFilename), kTestResourceMimeType);
Element* target = GetDocument().getElementById(AtomicString("target"));
ASSERT_TRUE(target);
ASSERT_FALSE(target->ComputedStyleRef().BackgroundLayers().GetImage());
target->setAttribute(html_names::kClassAttr, AtomicString("rule1"));
UpdateAllLifecyclePhasesForTest();
url_test_helpers::ServeAsynchronousRequests();
StyleImage* rule1_image =
target->ComputedStyleRef().BackgroundLayers().GetImage();
EXPECT_TRUE(rule1_image);
EXPECT_FALSE(rule1_image->ErrorOccurred());
target->setAttribute(html_names::kClassAttr, AtomicString("rule2"));
UpdateAllLifecyclePhasesForTest();
url_test_helpers::ServeAsynchronousRequests();
StyleImage* rule2_image =
target->ComputedStyleRef().BackgroundLayers().GetImage();
EXPECT_EQ(*rule1_image, *rule2_image);
EXPECT_FALSE(rule2_image->ErrorOccurred());
}
TEST_F(StyleImageCacheWithLoadingTest, LoadFailedBackgroundImageURL) {
SetBodyInnerHTML(R"HTML(
<style>
.rule1 { background-image: url(http://test.com/background_image.png) }
.rule2 { background-image: url(http://test.com/background_image.png) }
</style>
<div id="target"></div>
)HTML");
const auto image_url =
url_test_helpers::ToKURL("http://test.com/background_image.png");
url_test_helpers::RegisterMockedErrorURLLoad(image_url);
Element* target = GetDocument().getElementById(AtomicString("target"));
ASSERT_TRUE(target);
ASSERT_FALSE(target->ComputedStyleRef().BackgroundLayers().GetImage());
target->setAttribute(html_names::kClassAttr, AtomicString("rule1"));
UpdateAllLifecyclePhasesForTest();
url_test_helpers::ServeAsynchronousRequests();
StyleImage* rule1_image1 =
target->ComputedStyleRef().BackgroundLayers().GetImage();
EXPECT_TRUE(rule1_image1->ErrorOccurred());
url_test_helpers::RegisterMockedURLUnregister(image_url);
url_test_helpers::RegisterMockedURLLoad(
image_url, test::CoreTestDataPath(kTestResourceFilename),
kTestResourceMimeType);
target->setAttribute(html_names::kClassAttr, AtomicString("rule2"));
UpdateAllLifecyclePhasesForTest();
url_test_helpers::ServeAsynchronousRequests();
StyleImage* rule1_image2 =
target->ComputedStyleRef().BackgroundLayers().GetImage();
EXPECT_NE(*rule1_image1, *rule1_image2);
EXPECT_FALSE(rule1_image2->ErrorOccurred());
EXPECT_TRUE(FetchedImageMap().Contains(image_url.GetString()));
EXPECT_EQ(FetchedImageMap().size(), 1u);
}
} // namespace blink