Skip to content

Commit

Permalink
Add document-wide cache for StyleFetchedImage
Browse files Browse the repository at this point in the history
StyleFetchedImage is cached on the CSSImageValue object and re-used each
time a declaration applies to an element's ComputedStyle. The problem
arises when the same url is used in different declarations, or even
worse, when the CSSImageValue is regenerated every time when a custom
property url is substituted into another property. In those cases we
create a new StyleFetchedImage and re-fetch the resource.

Use the same StyleFetchedImage for the cached image url for all
CSSImageValue objects for the same Document. The cache is a map from url
into a weak StyleFetchedImage such that it will be garbage collected
once there are no more CSSImageValues referencing the StyleFetchedImage.

Bug: 961600
Change-Id: I5978c9b6befc9b0aa2f01e7e764866e2d80a8011
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3565368
Reviewed-by: Anders Hartvoll Ruud <andruud@chromium.org>
Commit-Queue: Rune Lillesveen <futhark@chromium.org>
Cr-Commit-Position: refs/heads/main@{#988488}
  • Loading branch information
Rune Lillesveen authored and Chromium LUCI CQ committed Apr 4, 2022
1 parent 6e6b034 commit 288ae84
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 6 deletions.
3 changes: 3 additions & 0 deletions third_party/blink/renderer/core/css/build.gni
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,8 @@ blink_core_sources_css = [
"property_registry.h",
"property_set_css_style_declaration.cc",
"property_set_css_style_declaration.h",
"style_image_cache.cc",
"style_image_cache.h",
"style_request.h",
"remote_font_face_source.cc",
"remote_font_face_source.h",
Expand Down Expand Up @@ -767,6 +769,7 @@ blink_core_tests_css = [
"selector_query_test.cc",
"style_element_test.cc",
"style_engine_test.cc",
"style_image_cache_test.cc",
"style_recalc_change_test.cc",
"style_recalc_context_test.cc",
"style_environment_variables_test.cc",
Expand Down
7 changes: 3 additions & 4 deletions third_party/blink/renderer/core/css/css_image_value.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "third_party/blink/public/common/loader/referrer_utils.h"
#include "third_party/blink/public/web/web_local_frame_client.h"
#include "third_party/blink/renderer/core/css/css_markup.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/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
Expand Down Expand Up @@ -126,10 +127,8 @@ StyleImage* CSSImageValue::CacheImage(

FetchParameters params =
PrepareFetch(document, image_request_behavior, cross_origin);
cached_image_ = MakeGarbageCollected<StyleFetchedImage>(
ImageResourceContent::Fetch(params, document.Fetcher()), document,
params.GetImageRequestBehavior() == FetchParameters::kDeferImageLoad,
origin_clean_ == OriginClean::kTrue, is_ad_related_, params.Url());
cached_image_ = document.GetStyleEngine().CacheStyleImage(
params, origin_clean_, is_ad_related_);
}
return cached_image_.Get();
}
Expand Down
1 change: 1 addition & 0 deletions third_party/blink/renderer/core/css/style_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3179,6 +3179,7 @@ void StyleEngine::Trace(Visitor* visitor) const {
visitor->Trace(vtt_originating_element_);
visitor->Trace(parent_for_detached_subtree_);
visitor->Trace(ua_document_transition_style_);
visitor->Trace(style_image_cache_);
FontSelectorClient::Trace(visitor);
}

Expand Down
13 changes: 13 additions & 0 deletions third_party/blink/renderer/core/css/style_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include "third_party/blink/renderer/core/css/layout_tree_rebuild_root.h"
#include "third_party/blink/renderer/core/css/pending_sheet_type.h"
#include "third_party/blink/renderer/core/css/rule_feature_set.h"
#include "third_party/blink/renderer/core/css/style_image_cache.h"
#include "third_party/blink/renderer/core/css/style_invalidation_root.h"
#include "third_party/blink/renderer/core/css/style_recalc_root.h"
#include "third_party/blink/renderer/core/css/vision_deficiency.h"
Expand Down Expand Up @@ -554,6 +555,13 @@ class CORE_EXPORT StyleEngine final : public GarbageCollected<StyleEngine>,
return document_transition_tags_;
}

StyleFetchedImage* CacheStyleImage(FetchParameters& params,
OriginClean origin_clean,
bool is_ad_related) {
return style_image_cache_.CacheStyleImage(GetDocument(), params,
origin_clean, is_ad_related);
}

void Trace(Visitor*) const override;
const char* NameInHeapSnapshot() const override { return "StyleEngine"; }

Expand Down Expand Up @@ -886,6 +894,7 @@ class CORE_EXPORT StyleEngine final : public GarbageCollected<StyleEngine>,
friend class StyleEngineTest;
friend class WhitespaceAttacherTest;
friend class StyleCascadeTest;
friend class StyleImageCacheTest;

HeapHashSet<Member<TextTrack>> text_tracks_;
Member<Element> vtt_originating_element_;
Expand All @@ -899,6 +908,10 @@ class CORE_EXPORT StyleEngine final : public GarbageCollected<StyleEngine>,
// The set of IDs for which ::page-transition-container pseudo elements are
// generated during a DocumentTransition.
Vector<AtomicString> document_transition_tags_;

// Cache for sharing StyleFetchedImage between CSSValues referencing the same
// URL.
StyleImageCache style_image_cache_;
};

} // namespace blink
Expand Down
32 changes: 32 additions & 0 deletions third_party/blink/renderer/core/css/style_image_cache.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright 2022 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/style_image_cache.h"

#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h"
#include "third_party/blink/renderer/core/style/style_fetched_image.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"

namespace blink {

StyleFetchedImage* StyleImageCache::CacheStyleImage(Document& document,
FetchParameters& params,
OriginClean origin_clean,
bool is_ad_related) {
auto result = fetched_image_map_.insert(params.Url(), nullptr);
if (result.is_new_entry || !result.stored_value->value) {
result.stored_value->value = MakeGarbageCollected<StyleFetchedImage>(
ImageResourceContent::Fetch(params, document.Fetcher()), document,
params.GetImageRequestBehavior() == FetchParameters::kDeferImageLoad,
origin_clean == OriginClean::kTrue, is_ad_related, params.Url());
}
return result.stored_value->value;
}

void StyleImageCache::Trace(Visitor* visitor) const {
visitor->Trace(fetched_image_map_);
}

} // namespace blink
50 changes: 50 additions & 0 deletions third_party/blink/renderer/core/css/style_image_cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2022 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.

#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_STYLE_IMAGE_CACHE_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_STYLE_IMAGE_CACHE_H_

#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/css/css_origin_clean.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/kurl_hash.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"

namespace blink {

class Document;
class FetchParameters;
class StyleFetchedImage;

// A per-StyleEngine cache for StyleImages. A CSSImageValue points to a
// StyleImage, but different CSSImageValue objects with the same URL would not
// have shared the same StyleImage without this cache.
class CORE_EXPORT StyleImageCache {
DISALLOW_NEW();

public:
StyleImageCache() = default;

// Look up an existing StyleFetchedImage in the cache, or create a new one,
// add it to the cache, and start the fetch.
StyleFetchedImage* CacheStyleImage(Document&,
FetchParameters&,
OriginClean,
bool is_ad_related);

void Trace(Visitor*) const;

private:
// Map from URL to style image. A weak reference makes sure the entry is
// removed when no style declarations nor computed styles have a reference to
// the image.
HeapHashMap<KURL, WeakMember<StyleFetchedImage>> fetched_image_map_;

friend class StyleImageCacheTest;
};

} // namespace blink

#endif // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_STYLE_IMAGE_CACHE_H_
109 changes: 109 additions & 0 deletions third_party/blink/renderer/core/css/style_image_cache_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright 2022 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/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/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"

namespace blink {

class StyleImageCacheTest : public PageTestBase {
protected:
void SetUp() override {
PageTestBase::SetUp();
GetDocument().SetBaseURLOverride(KURL("http://test.com"));
}
const HeapHashMap<KURL, WeakMember<StyleFetchedImage>>& 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("target");
ASSERT_TRUE(target);
ASSERT_FALSE(target->ComputedStyleRef().BackgroundLayers().GetImage());

target->setAttribute(blink::html_names::kClassAttr, "rule1");
UpdateAllLifecyclePhasesForTest();

StyleImage* rule1_image =
target->ComputedStyleRef().BackgroundLayers().GetImage();
EXPECT_TRUE(rule1_image);

target->setAttribute(blink::html_names::kClassAttr, "rule2");
UpdateAllLifecyclePhasesForTest();

StyleImage* rule2_image =
target->ComputedStyleRef().BackgroundLayers().GetImage();
EXPECT_EQ(rule1_image, rule2_image);
}

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("target");

StyleImage* initial_image =
target->ComputedStyleRef().BackgroundLayers().GetImage();
EXPECT_TRUE(initial_image);

target->setAttribute(blink::html_names::kClassAttr, "green");
UpdateAllLifecyclePhasesForTest();

StyleImage* image_after_recalc =
target->ComputedStyleRef().BackgroundLayers().GetImage();
EXPECT_EQ(initial_image, image_after_recalc);
}

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(KURL("http://test.com/url.png")));
EXPECT_TRUE(FetchedImageMap().Contains(KURL("http://test.com/url2.png")));
EXPECT_EQ(FetchedImageMap().size(), 2u);

Element* sheet = GetDocument().getElementById("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(KURL("http://test.com/url.png")));
EXPECT_FALSE(FetchedImageMap().Contains(KURL("http://test.com/url2.png")));
EXPECT_EQ(FetchedImageMap().size(), 0u);
}

} // namespace blink
4 changes: 2 additions & 2 deletions third_party/blink/renderer/core/style/style_fetched_image.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ class Document;

// This class represents an <image> that loads a single image resource (the
// url(...) function.)
class StyleFetchedImage final : public StyleImage,
public ImageResourceObserver {
class CORE_EXPORT StyleFetchedImage final : public StyleImage,
public ImageResourceObserver {
USING_PRE_FINALIZER(StyleFetchedImage, Prefinalize);

public:
Expand Down

0 comments on commit 288ae84

Please sign in to comment.