Commit 9e26744c authored by Xiaocheng Hu's avatar Xiaocheng Hu Committed by Commit Bot

Make sure 'font-display: optional' doesn't cause relayout

This patch ensures that an 'optional' web font never causes relayout:

- When document rendering starts, any 'optional' web font that hasn't
  been loaded enters the failure period directly.

- When an @font-face rule is added, if document rendering has started
  and the font is not available from memory cache, then it enters the
  failure period directly.

In this way, any element attempting to use such 'optional' fonts will
always use a font that's further down in the 'font-family' list during
the document's lifetime.

Bug: 1040632
Change-Id: Idde675a8dddadf15d22c13b95db649410fd9cd0e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2057502Reviewed-by: default avatarKunihiko Sakamoto <ksakamoto@chromium.org>
Reviewed-by: default avatarChris Harrelson <chrishtr@chromium.org>
Reviewed-by: default avatarTakashi Toyoshima <toyoshim@chromium.org>
Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Commit-Queue: Xiaocheng Hu <xiaochengh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#749080}
parent f676aa95
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "third_party/blink/renderer/core/css/remote_font_face_source.h" #include "third_party/blink/renderer/core/css/remote_font_face_source.h"
#include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_functions.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom-blink.h" #include "third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom-blink.h"
#include "third_party/blink/public/platform/task_type.h" #include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/platform/web_effective_connection_type.h" #include "third_party/blink/public/platform/web_effective_connection_type.h"
...@@ -27,58 +28,71 @@ ...@@ -27,58 +28,71 @@
namespace blink { namespace blink {
namespace { RemoteFontFaceSource::DisplayPeriod RemoteFontFaceSource::ComputePeriod()
const {
RemoteFontFaceSource::DisplayPeriod ComputePeriod( switch (display_) {
FontDisplay displayValue,
RemoteFontFaceSource::Phase phase,
bool is_intervention_triggered) {
switch (displayValue) {
case kFontDisplayAuto: case kFontDisplayAuto:
if (is_intervention_triggered) if (is_intervention_triggered_)
return RemoteFontFaceSource::kSwapPeriod; return kSwapPeriod;
FALLTHROUGH; FALLTHROUGH;
case kFontDisplayBlock: case kFontDisplayBlock:
switch (phase) { switch (phase_) {
case RemoteFontFaceSource::kNoLimitExceeded: case kNoLimitExceeded:
case RemoteFontFaceSource::kShortLimitExceeded: case kShortLimitExceeded:
return RemoteFontFaceSource::kBlockPeriod; return kBlockPeriod;
case RemoteFontFaceSource::kLongLimitExceeded: case kLongLimitExceeded:
return RemoteFontFaceSource::kSwapPeriod; return kSwapPeriod;
} }
case kFontDisplaySwap: case kFontDisplaySwap:
return RemoteFontFaceSource::kSwapPeriod; return kSwapPeriod;
case kFontDisplayFallback: case kFontDisplayFallback:
switch (phase) { switch (phase_) {
case RemoteFontFaceSource::kNoLimitExceeded: case kNoLimitExceeded:
return RemoteFontFaceSource::kBlockPeriod; return kBlockPeriod;
case RemoteFontFaceSource::kShortLimitExceeded: case kShortLimitExceeded:
return RemoteFontFaceSource::kSwapPeriod; return kSwapPeriod;
case RemoteFontFaceSource::kLongLimitExceeded: case kLongLimitExceeded:
return RemoteFontFaceSource::kFailurePeriod; return kFailurePeriod;
}
case kFontDisplayOptional: {
const bool use_phase_value =
!base::FeatureList::IsEnabled(
features::kFontPreloadingDelaysRendering) ||
!GetDocument();
if (use_phase_value) {
switch (phase_) {
case kNoLimitExceeded:
return kBlockPeriod;
case kShortLimitExceeded:
case kLongLimitExceeded:
return kFailurePeriod;
}
} }
case kFontDisplayOptional: // We simply skip the block period, as we should never render invisible
switch (phase) { // fallback for 'font-display: optional'.
case RemoteFontFaceSource::kNoLimitExceeded:
return RemoteFontFaceSource::kBlockPeriod; if (GetDocument()->GetFontPreloadManager().RenderingHasBegun()) {
case RemoteFontFaceSource::kShortLimitExceeded: if (FinishedFromMemoryCache() ||
case RemoteFontFaceSource::kLongLimitExceeded: finished_before_document_rendering_begin_)
return RemoteFontFaceSource::kFailurePeriod; return kSwapPeriod;
return kFailurePeriod;
} }
return kSwapPeriod;
}
case kFontDisplayEnumMax: case kFontDisplayEnumMax:
NOTREACHED(); NOTREACHED();
break; break;
} }
NOTREACHED(); NOTREACHED();
return RemoteFontFaceSource::kSwapPeriod; return kSwapPeriod;
} }
} // namespace
RemoteFontFaceSource::RemoteFontFaceSource(CSSFontFace* css_font_face, RemoteFontFaceSource::RemoteFontFaceSource(CSSFontFace* css_font_face,
FontSelector* font_selector, FontSelector* font_selector,
FontDisplay display) FontDisplay display)
...@@ -90,13 +104,18 @@ RemoteFontFaceSource::RemoteFontFaceSource(CSSFontFace* css_font_face, ...@@ -90,13 +104,18 @@ RemoteFontFaceSource::RemoteFontFaceSource(CSSFontFace* css_font_face,
font_selector, font_selector,
ReportOptions::kDoNotReport)), ReportOptions::kDoNotReport)),
phase_(kNoLimitExceeded), phase_(kNoLimitExceeded),
is_intervention_triggered_(ShouldTriggerWebFontsIntervention()) { is_intervention_triggered_(ShouldTriggerWebFontsIntervention()),
finished_before_document_rendering_begin_(false) {
DCHECK(face_); DCHECK(face_);
period_ = ComputePeriod(display_, phase_, is_intervention_triggered_); period_ = ComputePeriod();
} }
RemoteFontFaceSource::~RemoteFontFaceSource() = default; RemoteFontFaceSource::~RemoteFontFaceSource() = default;
Document* RemoteFontFaceSource::GetDocument() const {
return Document::DynamicFrom(font_selector_->GetExecutionContext());
}
void RemoteFontFaceSource::Dispose() { void RemoteFontFaceSource::Dispose() {
ClearResource(); ClearResource();
PruneTable(); PruneTable();
...@@ -147,6 +166,17 @@ void RemoteFontFaceSource::NotifyFinished(Resource* resource) { ...@@ -147,6 +166,17 @@ void RemoteFontFaceSource::NotifyFinished(Resource* resource) {
ClearResource(); ClearResource();
PruneTable(); PruneTable();
if (GetDocument() &&
!GetDocument()->GetFontPreloadManager().RenderingHasBegun()) {
finished_before_document_rendering_begin_ = true;
}
if (FinishedFromMemoryCache())
period_ = kNotApplicablePeriod;
else
UpdatePeriod();
if (face_->FontLoaded(this)) { if (face_->FontLoaded(this)) {
font_selector_->FontFaceInvalidated(); font_selector_->FontFaceInvalidated();
...@@ -187,8 +217,7 @@ void RemoteFontFaceSource::SetDisplay(FontDisplay display) { ...@@ -187,8 +217,7 @@ void RemoteFontFaceSource::SetDisplay(FontDisplay display) {
} }
void RemoteFontFaceSource::UpdatePeriod() { void RemoteFontFaceSource::UpdatePeriod() {
DisplayPeriod new_period = DisplayPeriod new_period = ComputePeriod();
ComputePeriod(display_, phase_, is_intervention_triggered_);
// Fallback font is invisible iff the font is loading and in the block period. // Fallback font is invisible iff the font is loading and in the block period.
// Invalidate the font if its fallback visibility has changed. // Invalidate the font if its fallback visibility has changed.
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
namespace blink { namespace blink {
class CSSFontFace; class CSSFontFace;
class Document;
class FontSelector; class FontSelector;
class FontCustomPlatformData; class FontCustomPlatformData;
...@@ -23,9 +24,6 @@ class RemoteFontFaceSource final : public CSSFontFaceSource, ...@@ -23,9 +24,6 @@ class RemoteFontFaceSource final : public CSSFontFaceSource,
public: public:
enum Phase { kNoLimitExceeded, kShortLimitExceeded, kLongLimitExceeded }; enum Phase { kNoLimitExceeded, kShortLimitExceeded, kLongLimitExceeded };
// Periods of the Font Display Timeline.
// https://drafts.csswg.org/css-fonts-4/#font-display-timeline
enum DisplayPeriod { kBlockPeriod, kSwapPeriod, kFailurePeriod };
RemoteFontFaceSource(CSSFontFace*, FontSelector*, FontDisplay); RemoteFontFaceSource(CSSFontFace*, FontSelector*, FontDisplay);
~RemoteFontFaceSource() override; ~RemoteFontFaceSource() override;
...@@ -60,6 +58,19 @@ class RemoteFontFaceSource final : public CSSFontFaceSource, ...@@ -60,6 +58,19 @@ class RemoteFontFaceSource final : public CSSFontFaceSource,
const FontDescription&); const FontDescription&);
private: private:
// Periods of the Font Display Timeline.
// https://drafts.csswg.org/css-fonts-4/#font-display-timeline
// Note that kNotApplicablePeriod is an implementation detail indicating that
// the font is loaded from memory cache synchronously, and hence, made
// immediately available. As we never need to use a fallback for it, using
// other DisplayPeriod values seem artificial. So we use a special value.
enum DisplayPeriod {
kBlockPeriod,
kSwapPeriod,
kFailurePeriod,
kNotApplicablePeriod
};
class FontLoadHistograms { class FontLoadHistograms {
DISALLOW_NEW(); DISALLOW_NEW();
...@@ -113,6 +124,9 @@ class RemoteFontFaceSource final : public CSSFontFaceSource, ...@@ -113,6 +124,9 @@ class RemoteFontFaceSource final : public CSSFontFaceSource,
DataSource data_source_; DataSource data_source_;
}; };
Document* GetDocument() const;
DisplayPeriod ComputePeriod() const;
void UpdatePeriod(); void UpdatePeriod();
bool ShouldTriggerWebFontsIntervention(); bool ShouldTriggerWebFontsIntervention();
bool IsLowPriorityLoadingAllowedForRemoteFont() const override; bool IsLowPriorityLoadingAllowedForRemoteFont() const override;
...@@ -132,6 +146,7 @@ class RemoteFontFaceSource final : public CSSFontFaceSource, ...@@ -132,6 +146,7 @@ class RemoteFontFaceSource final : public CSSFontFaceSource,
DisplayPeriod period_; DisplayPeriod period_;
FontLoadHistograms histograms_; FontLoadHistograms histograms_;
bool is_intervention_triggered_; bool is_intervention_triggered_;
bool finished_before_document_rendering_begin_;
}; };
} // namespace blink } // namespace blink
......
...@@ -111,9 +111,6 @@ void FontPreloadManager::WillBeginRendering() { ...@@ -111,9 +111,6 @@ void FontPreloadManager::WillBeginRendering() {
state_ = State::kUnblocked; state_ = State::kUnblocked;
finish_observers_.clear(); finish_observers_.clear();
// TODO(xiaochengh): Mark all 'font-display: optional' fonts that are still
// loading as failure.
} }
void FontPreloadManager::FontPreloadingDelaysRenderingTimerFired(TimerBase*) { void FontPreloadManager::FontPreloadingDelaysRenderingTimerFired(TimerBase*) {
......
...@@ -31,6 +31,7 @@ class CORE_EXPORT FontPreloadManager final { ...@@ -31,6 +31,7 @@ class CORE_EXPORT FontPreloadManager final {
bool HasPendingRenderBlockingFonts() const; bool HasPendingRenderBlockingFonts() const;
void WillBeginRendering(); void WillBeginRendering();
bool RenderingHasBegun() const { return state_ == State::kUnblocked; }
void FontPreloadingStarted(FontResource*); void FontPreloadingStarted(FontResource*);
void FontPreloadingFinished(FontResource*, ResourceFinishObserver*); void FontPreloadingFinished(FontResource*, ResourceFinishObserver*);
......
...@@ -6,7 +6,11 @@ ...@@ -6,7 +6,11 @@
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "third_party/blink/public/common/features.h" #include "third_party/blink/public/common/features.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/html/html_head_element.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/testing/sim/sim_request.h" #include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h" #include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
...@@ -21,6 +25,11 @@ class FontPreloadManagerTest : public SimTest { ...@@ -21,6 +25,11 @@ class FontPreloadManagerTest : public SimTest {
SimTest::SetUp(); SimTest::SetUp();
} }
static Vector<char> ReadAhemWoff2() {
return test::ReadFromFile(test::CoreTestDataPath("Ahem.woff2"))
->CopyAs<Vector<char>>();
}
protected: protected:
FontPreloadManager& GetFontPreloadManager() { FontPreloadManager& GetFontPreloadManager() {
return GetDocument().GetFontPreloadManager(); return GetDocument().GetFontPreloadManager();
...@@ -29,6 +38,12 @@ class FontPreloadManagerTest : public SimTest { ...@@ -29,6 +38,12 @@ class FontPreloadManagerTest : public SimTest {
using State = FontPreloadManager::State; using State = FontPreloadManager::State;
State GetState() { return GetFontPreloadManager().state_; } State GetState() { return GetFontPreloadManager().state_; }
Element* GetTarget() { return GetDocument().getElementById("target"); }
const Font& GetTargetFont() {
return GetTarget()->GetLayoutObject()->Style()->GetFont();
}
private: private:
base::test::ScopedFeatureList scoped_feature_list_; base::test::ScopedFeatureList scoped_feature_list_;
}; };
...@@ -170,4 +185,202 @@ TEST_F(FontPreloadManagerTest, SlowFontTimeoutAfterBody) { ...@@ -170,4 +185,202 @@ TEST_F(FontPreloadManagerTest, SlowFontTimeoutAfterBody) {
font_resource.Finish(); font_resource.Finish();
} }
// A trivial test case to verify test setup
TEST_F(FontPreloadManagerTest, RegularWebFont) {
SimRequest main_resource("https://example.com", "text/html");
SimRequest font_resource("https://example.com/Ahem.woff2", "font/woff2");
LoadURL("https://example.com");
main_resource.Complete(R"HTML(
<!doctype html>
<style>
@font-face {
font-family: custom-font;
src: url(https://example.com/Ahem.woff2) format("woff2");
}
#target {
font: 25px/1 custom-font, monospace;
}
</style>
<span id=target style="position:relative">0123456789</span>
)HTML");
// Now rendering has started, as there's no blocking resources.
EXPECT_FALSE(Compositor().DeferMainFrameUpdate());
EXPECT_EQ(State::kUnblocked, GetState());
font_resource.Complete(ReadAhemWoff2());
// Now everything is loaded. The web font should be used in rendering.
Compositor().BeginFrame().DrawCount();
EXPECT_EQ(250, GetTarget()->OffsetWidth());
EXPECT_FALSE(GetTargetFont().ShouldSkipDrawing());
}
TEST_F(FontPreloadManagerTest, OptionalFontWithoutPreloading) {
SimRequest main_resource("https://example.com", "text/html");
SimRequest font_resource("https://example.com/Ahem.woff2", "font/woff2");
LoadURL("https://example.com");
main_resource.Complete(R"HTML(
<!doctype html>
<style>
@font-face {
font-family: custom-font;
src: url(https://example.com/Ahem.woff2) format("woff2");
font-display: optional;
}
#target {
font: 25px/1 custom-font, monospace;
}
</style>
<span id=target>0123456789</span>
)HTML");
// Now rendering has started, as there's no blocking resources.
EXPECT_FALSE(Compositor().DeferMainFrameUpdate());
EXPECT_EQ(State::kUnblocked, GetState());
font_resource.Complete(ReadAhemWoff2());
// The 'optional' web font isn't used, as it didn't finish loading before
// rendering started. Text is rendered in visible fallback.
Compositor().BeginFrame().Contains(SimCanvas::kText);
EXPECT_GT(250, GetTarget()->OffsetWidth());
EXPECT_FALSE(GetTargetFont().ShouldSkipDrawing());
}
TEST_F(FontPreloadManagerTest, OptionalFontRemoveAndReadd) {
SimRequest main_resource("https://example.com", "text/html");
SimRequest font_resource("https://example.com/Ahem.woff2", "font/woff2");
LoadURL("https://example.com");
main_resource.Complete(R"HTML(
<!doctype html>
<style>
@font-face {
font-family: custom-font;
src: url(https://example.com/Ahem.woff2) format("woff2");
font-display: optional;
}
#target {
font: 25px/1 custom-font, monospace;
}
</style>
<span id=target>0123456789</span>
)HTML");
// Now rendering has started, as there's no blocking resources.
EXPECT_FALSE(Compositor().DeferMainFrameUpdate());
EXPECT_EQ(State::kUnblocked, GetState());
// The 'optional' web font isn't used, as it didn't finish loading before
// rendering started. Text is rendered in visible fallback.
Compositor().BeginFrame().Contains(SimCanvas::kText);
EXPECT_GT(250, GetTarget()->OffsetWidth());
EXPECT_FALSE(GetTargetFont().ShouldSkipDrawing());
font_resource.Complete(ReadAhemWoff2());
Element* style = GetDocument().QuerySelector("style");
style->remove();
GetDocument().head()->appendChild(style);
// After removing and readding the style sheet, we've created a new font face
// that got loaded immediately from the memory cache. So it can be used.
Compositor().BeginFrame().Contains(SimCanvas::kText);
EXPECT_EQ(250, GetTarget()->OffsetWidth());
EXPECT_FALSE(GetTargetFont().ShouldSkipDrawing());
}
TEST_F(FontPreloadManagerTest, OptionalFontSlowPreloading) {
SimRequest main_resource("https://example.com", "text/html");
SimRequest font_resource("https://example.com/Ahem.woff2", "font/woff2");
LoadURL("https://example.com");
main_resource.Complete(R"HTML(
<!doctype html>
<link rel="preload" as="font" type="font/woff2"
href="https://example.com/Ahem.woff2" crossorigin>
<style>
@font-face {
font-family: custom-font;
src: url(https://example.com/Ahem.woff2) format("woff2");
font-display: optional;
}
#target {
font: 25px/1 custom-font, monospace;
}
</style>
<span id=target>0123456789</span>
)HTML");
// Rendering is blocked due to font being preloaded.
EXPECT_TRUE(Compositor().DeferMainFrameUpdate());
EXPECT_TRUE(GetFontPreloadManager().HasPendingRenderBlockingFonts());
EXPECT_EQ(State::kLoading, GetState());
GetFontPreloadManager().FontPreloadingDelaysRenderingTimerFired(nullptr);
// Rendering is unblocked after the font preloading has timed out.
EXPECT_FALSE(Compositor().DeferMainFrameUpdate());
EXPECT_FALSE(GetFontPreloadManager().HasPendingRenderBlockingFonts());
EXPECT_EQ(State::kUnblocked, GetState());
// First frame renders text with visible fallback, as the 'optional' web font
// isn't loaded yet, and should be treated as in the failure period.
Compositor().BeginFrame();
EXPECT_GT(250, GetTarget()->OffsetWidth());
EXPECT_FALSE(GetTargetFont().ShouldSkipDrawing());
font_resource.Complete(ReadAhemWoff2());
// The 'optional' web font should not cause relayout even if it finishes
// loading now.
Compositor().BeginFrame();
EXPECT_GT(250, GetTarget()->OffsetWidth());
EXPECT_FALSE(GetTargetFont().ShouldSkipDrawing());
}
TEST_F(FontPreloadManagerTest, OptionalFontFastPreloading) {
SimRequest main_resource("https://example.com", "text/html");
SimRequest font_resource("https://example.com/Ahem.woff2", "font/woff2");
LoadURL("https://example.com");
main_resource.Complete(R"HTML(
<!doctype html>
<link rel="preload" as="font" type="font/woff2"
href="https://example.com/Ahem.woff2" crossorigin>
<style>
@font-face {
font-family: custom-font;
src: url(https://example.com/Ahem.woff2) format("woff2");
font-display: optional;
}
#target {
font: 25px/1 custom-font, monospace;
}
</style>
<span id=target>0123456789</span>
)HTML");
// Rendering is blocked due to font being preloaded.
EXPECT_TRUE(Compositor().DeferMainFrameUpdate());
EXPECT_TRUE(GetFontPreloadManager().HasPendingRenderBlockingFonts());
EXPECT_EQ(State::kLoading, GetState());
font_resource.Complete(ReadAhemWoff2());
test::RunPendingTasks();
// Rendering is unblocked after the font is preloaded.
EXPECT_FALSE(Compositor().DeferMainFrameUpdate());
EXPECT_FALSE(GetFontPreloadManager().HasPendingRenderBlockingFonts());
EXPECT_EQ(State::kUnblocked, GetState());
// The 'optional' web font should be used in the first paint.
Compositor().BeginFrame();
EXPECT_EQ(250, GetTarget()->OffsetWidth());
EXPECT_FALSE(GetTargetFont().ShouldSkipDrawing());
}
} // namespace blink } // namespace blink
...@@ -567,22 +567,23 @@ String Resource::ReasonNotDeletable() const { ...@@ -567,22 +567,23 @@ String Resource::ReasonNotDeletable() const {
return builder.ToString(); return builder.ToString();
} }
void Resource::DidAddClient(ResourceClient* c) { void Resource::DidAddClient(ResourceClient* client) {
if (scoped_refptr<SharedBuffer> data = Data()) { if (scoped_refptr<SharedBuffer> data = Data()) {
for (const auto& span : *data) { for (const auto& span : *data) {
c->DataReceived(this, span.data(), span.size()); client->DataReceived(this, span.data(), span.size());
// Stop pushing data if the client removed itself. // Stop pushing data if the client removed itself.
if (!HasClient(c)) if (!HasClient(client))
break; break;
} }
} }
if (!HasClient(c)) if (!HasClient(client))
return; return;
if (IsFinishedInternal()) { if (IsFinishedInternal()) {
c->NotifyFinished(this); client->SetHasFinishedFromMemoryCache();
if (clients_.Contains(c)) { client->NotifyFinished(this);
finished_clients_.insert(c); if (clients_.Contains(client)) {
clients_.erase(c); finished_clients_.insert(client);
clients_.erase(client);
} }
} }
} }
......
...@@ -64,6 +64,9 @@ class PLATFORM_EXPORT ResourceClient : public GarbageCollectedMixin { ...@@ -64,6 +64,9 @@ class PLATFORM_EXPORT ResourceClient : public GarbageCollectedMixin {
Resource* GetResource() const { return resource_; } Resource* GetResource() const { return resource_; }
bool FinishedFromMemoryCache() const { return finished_from_memory_cache_; }
void SetHasFinishedFromMemoryCache() { finished_from_memory_cache_ = true; }
// Name for debugging, e.g. shown in memory-infra. // Name for debugging, e.g. shown in memory-infra.
virtual String DebugName() const = 0; virtual String DebugName() const = 0;
...@@ -86,6 +89,10 @@ class PLATFORM_EXPORT ResourceClient : public GarbageCollectedMixin { ...@@ -86,6 +89,10 @@ class PLATFORM_EXPORT ResourceClient : public GarbageCollectedMixin {
base::SingleThreadTaskRunner* task_runner); base::SingleThreadTaskRunner* task_runner);
Member<Resource> resource_; Member<Resource> resource_;
// If true, the Resource was already available from the memory cache when this
// ResourceClient was setup, so that the request finished immediately.
bool finished_from_memory_cache_ = false;
}; };
} // namespace blink } // namespace blink
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment