Commit dfda8156 authored by rajendrant's avatar rajendrant Committed by Commit Bot

LazyLoad: Add basic support for lazily loading image elements

This CL adds basic support (behind a flag) for deferring the load of html image
elements until the user scrolls near them, in order to reduce network data
usage, memory usage, and speed up the loading of other content on the page.

LazyLoadImageObserver is created per document and that holds an
IntersectionObserver to observe the <img> elements within the document. When
the IntersectionObserver detects that the deferred image element is within a
specific distance threshold of the viewport, it resumes loading the image.

Subsequent CLs will defer CSS background images as well, and configure the
hardcoded distance-from-viewport threshold from field trial.

See the design docs below for more info.

LazyImages design doc:
https://docs.google.com/document/d/1jF1eSOhqTEt0L1WBCccGwH9chxLd9d1Ez0zo11obj14/edit

LazyLoad design doc:
https://docs.google.com/document/d/1e8ZbVyUwgIkQMvJma3kKUDg8UUkLRRdANStqKuOIvHg/edit

Bug: 846170
Change-Id: I8c1024a4cdb19f3c6c700ae49eec6369b67b72b4
Reviewed-on: https://chromium-review.googlesource.com/1132573
Commit-Queue: rajendrant <rajendrant@chromium.org>
Reviewed-by: default avatarFredrik Söderquist <fs@opera.com>
Reviewed-by: default avatarHiroshige Hayashizaki <hiroshige@chromium.org>
Cr-Commit-Position: refs/heads/master@{#583451}
parent 99255887
...@@ -4934,3 +4934,6 @@ crbug.com/873873 external/wpt/service-workers/service-worker/fetch-canvas-tainti ...@@ -4934,3 +4934,6 @@ crbug.com/873873 external/wpt/service-workers/service-worker/fetch-canvas-tainti
crbug.com/873873 virtual/outofblink-cors/external/wpt/service-workers/service-worker/fetch-canvas-tainting-video-cache.https.html [ Timeout Pass ] crbug.com/873873 virtual/outofblink-cors/external/wpt/service-workers/service-worker/fetch-canvas-tainting-video-cache.https.html [ Timeout Pass ]
crbug.com/873873 virtual/outofblink-cors-ns/external/wpt/service-workers/service-worker/fetch-canvas-tainting-video-cache.https.html [ Timeout Pass ] crbug.com/873873 virtual/outofblink-cors-ns/external/wpt/service-workers/service-worker/fetch-canvas-tainting-video-cache.https.html [ Timeout Pass ]
crbug.com/873873 virtual/service-worker-servicification/external/wpt/service-workers/service-worker/fetch-canvas-tainting-video-cache.https.html [ Timeout Pass ] crbug.com/873873 virtual/service-worker-servicification/external/wpt/service-workers/service-worker/fetch-canvas-tainting-video-cache.https.html [ Timeout Pass ]
# Tests that work only when LazyImageLoading feature is enabled.
crbug.com/846170 http/tests/lazyload/lazy.html [ Skip ]
...@@ -718,6 +718,11 @@ ...@@ -718,6 +718,11 @@
"base": "external/wpt/FileAPI/url", "base": "external/wpt/FileAPI/url",
"args": ["--enable-features=MojoBlobURLs"] "args": ["--enable-features=MojoBlobURLs"]
}, },
{
"prefix": "lazyload-image",
"base": "http/tests/lazyload",
"args": ["--enable-features=LazyImageLoading"]
},
{ {
"prefix": "origin-policy", "prefix": "origin-policy",
"base": "external/wpt/origin-policy", "base": "external/wpt/origin-policy",
......
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="placeholder.js"></script>
<body>
<div style="height:10000px;"></div>
</body>
<script>
async_test(function(t) {
const img = new Image();
img.onload = t.step_func_done(function() {
assert_true(is_image_fully_loaded(img));
});
img.src = '../loading/resources/base-image1.png';
}, "Test that load event is fired for JS Image() fetches");
async_test(function(t) {
const attached_img = document.createElement("IMG");
document.body.appendChild(attached_img);
attached_img.onload = t.step_func_done(function() {
assert_true(is_image_fully_loaded(attached_img));
});
attached_img.src = "../loading/resources/base-image2.png";
}, "Test that load event is fired for <img> created via JS and attached below viewport");
</script>
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="placeholder.js"></script>
<body>
<img id="in_viewport" src='../loading/resources/base-image1.png'>
<div style="height:10000px;"></div>
<img id="below_viewport" src='../loading/resources/base-image2.png'>
</body>
<script>
var in_viewport_element = document.getElementById("in_viewport");
var below_viewport_element = document.getElementById("below_viewport");
async_test(function(t) {
window.addEventListener("load", t.step_func_done());
}, "Test that document load event is fired");
async_test(function(t) {
in_viewport_element.addEventListener("load",
t.step_func_done(function() {
assert_true(is_image_fully_loaded(in_viewport_element));
}));
}, "Test that <img> in viewport is loaded, and not a placeholder");
async_test(function(t) {
var complete = 0;
var onload_callback = function() {
if (++complete == 2) {
// Document and the above viewport image has loaded.
assert_false(is_image_fully_loaded(below_viewport_element));
t.done();
}
};
window.addEventListener("load", t.step_func(onload_callback));
in_viewport_element.addEventListener("load",
t.step_func(onload_callback));
below_viewport_element.addEventListener("load",
t.unreached_func("Load event should not be fired for below viewport image"));
}, "Test that <img> below viewport is a placeholder, with lazyimage enabled");
</script>
// Returns if the image is complete and fully loaded as a non-placeholder image.
function is_image_fully_loaded(image) {
if (!image.complete) {
return false;
}
let canvas = document.createElement('canvas');
canvas.width = canvas.height = 1;
let canvasContext = canvas.getContext("2d");
canvasContext.drawImage(image, 0, 0);
let data = canvasContext.getImageData(0, 0, canvas.width, canvas.height).data;
// Fully loaded image should not be a placeholder which is drawn as a
// translucent gray rectangle in placeholder_image.cc
return data[0] != 0xd9 || data[1] != 0xd9 || data[2] != 0xd9;
}
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="placeholder.js"></script>
<body>
<img id="in_viewport" src='../loading/resources/base-image1.png'>
<div style="height:10000px;"></div>
<img id="below_viewport" src='../loading/resources/base-image2.png'>
</body>
<script>
var in_viewport_element = document.getElementById("in_viewport");
var below_viewport_element = document.getElementById("below_viewport");
async_test(function(t) {
window.addEventListener("load", t.step_func_done());
}, "Test that document load event is fired");
async_test(function(t) {
in_viewport_element.addEventListener("load",
t.step_func_done(function() {
assert_true(is_image_fully_loaded(in_viewport_element));
}));
}, "Test that <img> in viewport is loaded, and not a placeholder");
async_test(function(t) {
in_viewport_element.addEventListener("load", t.step_func(function() {
below_viewport_element.scrollIntoView();
}));
below_viewport_element.addEventListener("load",
t.step_func_done(function() {
assert_true(is_image_fully_loaded(below_viewport_element));
}));
}, "Test that <img> below viewport is loaded when scrolled near, and not a placeholder");
</script>
# This suite runs the tests in http/tests/lazyload with
# --enable-features=LazyImageLoading.
...@@ -183,6 +183,7 @@ ...@@ -183,6 +183,7 @@
#include "third_party/blink/renderer/core/html/html_unknown_element.h" #include "third_party/blink/renderer/core/html/html_unknown_element.h"
#include "third_party/blink/renderer/core/html/imports/html_import_loader.h" #include "third_party/blink/renderer/core/html/imports/html_import_loader.h"
#include "third_party/blink/renderer/core/html/imports/html_imports_controller.h" #include "third_party/blink/renderer/core/html/imports/html_imports_controller.h"
#include "third_party/blink/renderer/core/html/lazy_load_image_observer.h"
#include "third_party/blink/renderer/core/html/parser/html_document_parser.h" #include "third_party/blink/renderer/core/html/parser/html_document_parser.h"
#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
#include "third_party/blink/renderer/core/html/parser/nesting_level_incrementer.h" #include "third_party/blink/renderer/core/html/parser/nesting_level_incrementer.h"
...@@ -7421,6 +7422,7 @@ void Document::Trace(blink::Visitor* visitor) { ...@@ -7421,6 +7422,7 @@ void Document::Trace(blink::Visitor* visitor) {
visitor->Trace(policy_); visitor->Trace(policy_);
visitor->Trace(slot_assignment_engine_); visitor->Trace(slot_assignment_engine_);
visitor->Trace(viewport_data_); visitor->Trace(viewport_data_);
visitor->Trace(lazy_load_image_observer_);
Supplementable<Document>::Trace(visitor); Supplementable<Document>::Trace(visitor);
TreeScope::Trace(visitor); TreeScope::Trace(visitor);
ContainerNode::Trace(visitor); ContainerNode::Trace(visitor);
...@@ -7483,6 +7485,12 @@ bool Document::IsSlotAssignmentOrLegacyDistributionDirty() { ...@@ -7483,6 +7485,12 @@ bool Document::IsSlotAssignmentOrLegacyDistributionDirty() {
return false; return false;
} }
LazyLoadImageObserver& Document::EnsureLazyLoadImageObserver() {
if (!lazy_load_image_observer_)
lazy_load_image_observer_ = new LazyLoadImageObserver(*this);
return *lazy_load_image_observer_;
}
template class CORE_TEMPLATE_EXPORT Supplement<Document>; template class CORE_TEMPLATE_EXPORT Supplement<Document>;
} // namespace blink } // namespace blink
......
...@@ -136,6 +136,7 @@ class IntersectionObserverController; ...@@ -136,6 +136,7 @@ class IntersectionObserverController;
class LayoutPoint; class LayoutPoint;
class ReattachLegacyLayoutObjectList; class ReattachLegacyLayoutObjectList;
class LayoutView; class LayoutView;
class LazyLoadImageObserver;
class LiveNodeListBase; class LiveNodeListBase;
class LocalDOMWindow; class LocalDOMWindow;
class Locale; class Locale;
...@@ -1451,6 +1452,8 @@ class CORE_EXPORT Document : public ContainerNode, ...@@ -1451,6 +1452,8 @@ class CORE_EXPORT Document : public ContainerNode,
bool IsVerticalScrollEnforced() const { return is_vertical_scroll_enforced_; } bool IsVerticalScrollEnforced() const { return is_vertical_scroll_enforced_; }
LazyLoadImageObserver& EnsureLazyLoadImageObserver();
// TODO(binji): See http://crbug.com/798572. This implementation shares the // TODO(binji): See http://crbug.com/798572. This implementation shares the
// same agent cluster ID for any one document. The proper implementation of // same agent cluster ID for any one document. The proper implementation of
// this function must follow the rules described here: // this function must follow the rules described here:
...@@ -1890,6 +1893,8 @@ class CORE_EXPORT Document : public ContainerNode, ...@@ -1890,6 +1893,8 @@ class CORE_EXPORT Document : public ContainerNode,
// This is set through feature policy 'vertical-scroll'. // This is set through feature policy 'vertical-scroll'.
bool is_vertical_scroll_enforced_ = false; bool is_vertical_scroll_enforced_ = false;
Member<LazyLoadImageObserver> lazy_load_image_observer_;
// https://tc39.github.io/ecma262/#sec-agent-clusters // https://tc39.github.io/ecma262/#sec-agent-clusters
const base::UnguessableToken agent_cluster_id_; const base::UnguessableToken agent_cluster_id_;
}; };
......
...@@ -1276,6 +1276,20 @@ void LocalFrame::MaybeAllowImagePlaceholder(FetchParameters& params) const { ...@@ -1276,6 +1276,20 @@ void LocalFrame::MaybeAllowImagePlaceholder(FetchParameters& params) const {
} }
} }
bool LocalFrame::MaybeAllowLazyLoadingImage(FetchParameters& params) const {
if (!RuntimeEnabledFeatures::LazyImageLoadingEnabled())
return false;
if (params.GetPlaceholderImageRequestType() ==
FetchParameters::PlaceholderImageRequestType::kAllowPlaceholder) {
return false;
}
if (Owner() && !Owner()->ShouldLazyLoadChildren())
return false;
params.SetAllowImagePlaceholder();
return true;
}
WebURLLoaderFactory* LocalFrame::GetURLLoaderFactory() { WebURLLoaderFactory* LocalFrame::GetURLLoaderFactory() {
if (!url_loader_factory_) if (!url_loader_factory_)
url_loader_factory_ = Client()->CreateURLLoaderFactory(); url_loader_factory_ = Client()->CreateURLLoaderFactory();
......
...@@ -295,6 +295,11 @@ class CORE_EXPORT LocalFrame final : public Frame, ...@@ -295,6 +295,11 @@ class CORE_EXPORT LocalFrame final : public Frame,
// the embedder decides that Client Lo-Fi should be used for this request. // the embedder decides that Client Lo-Fi should be used for this request.
void MaybeAllowImagePlaceholder(FetchParameters&) const; void MaybeAllowImagePlaceholder(FetchParameters&) const;
// Convenience function to allow loading image placeholders for the request if
// lazyloading the image is possible. Returns if lazyloading the image is
// possible.
bool MaybeAllowLazyLoadingImage(FetchParameters&) const;
// The returned value is a off-heap raw-ptr and should not be stored. // The returned value is a off-heap raw-ptr and should not be stored.
WebURLLoaderFactory* GetURLLoaderFactory(); WebURLLoaderFactory* GetURLLoaderFactory();
......
...@@ -457,6 +457,8 @@ blink_core_sources("html") { ...@@ -457,6 +457,8 @@ blink_core_sources("html") {
"imports/link_import.h", "imports/link_import.h",
"lazy_load_frame_observer.cc", "lazy_load_frame_observer.cc",
"lazy_load_frame_observer.h", "lazy_load_frame_observer.h",
"lazy_load_image_observer.cc",
"lazy_load_image_observer.h",
"link_manifest.cc", "link_manifest.cc",
"link_manifest.h", "link_manifest.h",
"link_rel_attribute.cc", "link_rel_attribute.cc",
......
...@@ -87,6 +87,9 @@ class CORE_EXPORT HTMLImageElement final ...@@ -87,6 +87,9 @@ class CORE_EXPORT HTMLImageElement final
ImageResource* CachedImageResourceForImageDocument() const { ImageResource* CachedImageResourceForImageDocument() const {
return GetImageLoader().ImageResourceForImageDocument(); return GetImageLoader().ImageResourceForImageDocument();
} }
void LoadDeferredImage() {
GetImageLoader().LoadDeferredImage(referrer_policy_);
}
void SetImageForTest(ImageResourceContent* content) { void SetImageForTest(ImageResourceContent* content) {
GetImageLoader().SetImageForTest(content); GetImageLoader().SetImageForTest(content);
} }
...@@ -139,6 +142,8 @@ class CORE_EXPORT HTMLImageElement final ...@@ -139,6 +142,8 @@ class CORE_EXPORT HTMLImageElement final
FormAssociated* ToFormAssociatedOrNull() override { return this; }; FormAssociated* ToFormAssociatedOrNull() override { return this; };
void AssociateWith(HTMLFormElement*) override; void AssociateWith(HTMLFormElement*) override;
bool ElementCreatedByParser() const { return element_created_by_parser_; }
protected: protected:
// Controls how an image element appears in the layout. See: // Controls how an image element appears in the layout. See:
// https://html.spec.whatwg.org/multipage/embedded-content.html#image-request // https://html.spec.whatwg.org/multipage/embedded-content.html#image-request
......
// 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/html/lazy_load_image_observer.h"
#include "build/build_config.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/frame/local_frame.h"
#include "third_party/blink/renderer/core/html/html_image_element.h"
#include "third_party/blink/renderer/core/html_element_type_helpers.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/intersection_observer/intersection_observer.h"
#include "third_party/blink/renderer/core/intersection_observer/intersection_observer_entry.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
namespace blink {
void LazyLoadImageObserver::StartMonitoring(Element* element) {
if (LocalFrame* frame = element->GetDocument().GetFrame()) {
if (Document* document = frame->LocalFrameRoot().GetDocument()) {
document->EnsureLazyLoadImageObserver()
.lazy_load_intersection_observer_->observe(element);
}
}
}
void LazyLoadImageObserver::StopMonitoring(Element* element) {
if (LocalFrame* frame = element->GetDocument().GetFrame()) {
if (Document* document = frame->LocalFrameRoot().GetDocument()) {
document->EnsureLazyLoadImageObserver()
.lazy_load_intersection_observer_->unobserve(element);
}
}
}
LazyLoadImageObserver::LazyLoadImageObserver(Document& document) {
DCHECK(RuntimeEnabledFeatures::LazyImageLoadingEnabled());
document.AddConsoleMessage(ConsoleMessage::Create(
kInterventionMessageSource, kInfoMessageLevel,
"Images loaded lazily and replaced with placeholders. Load events are "
"deferred. See https://crbug.com/846170"));
lazy_load_intersection_observer_ = IntersectionObserver::Create(
{Length(kLazyLoadRootMarginPx, kFixed)},
{std::numeric_limits<float>::min()}, &document,
WTF::BindRepeating(&LazyLoadImageObserver::LoadIfNearViewport,
WrapWeakPersistent(this)));
}
void LazyLoadImageObserver::LoadIfNearViewport(
const HeapVector<Member<IntersectionObserverEntry>>& entries) {
DCHECK(!entries.IsEmpty());
for (auto entry : entries) {
if (!entry->isIntersecting())
continue;
Element* element = entry->target();
if (auto* image_element = ToHTMLImageElementOrNull(element))
image_element->LoadDeferredImage();
lazy_load_intersection_observer_->unobserve(element);
}
}
void LazyLoadImageObserver::Trace(Visitor* visitor) {
visitor->Trace(lazy_load_intersection_observer_);
}
} // namespace blink
// 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_LAZY_LOAD_IMAGE_OBSERVER_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_LAZY_LOAD_IMAGE_OBSERVER_H_
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/heap/heap_allocator.h"
#include "third_party/blink/renderer/platform/heap/member.h"
namespace blink {
class Document;
class Element;
class IntersectionObserver;
class IntersectionObserverEntry;
class Visitor;
class LazyLoadImageObserver final
: public GarbageCollected<LazyLoadImageObserver> {
public:
explicit LazyLoadImageObserver(Document&);
static void StartMonitoring(Element*);
static void StopMonitoring(Element*);
void Trace(Visitor*);
private:
// TODO(rajendrant): Make the root margins configurable via field trial params
// instead of just hardcoding the value here.
static constexpr int kLazyLoadRootMarginPx = 800;
void LoadIfNearViewport(const HeapVector<Member<IntersectionObserverEntry>>&);
// The intersection observer responsible for loading the image once it's near
// the viewport.
Member<IntersectionObserver> lazy_load_intersection_observer_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_HTML_LAZY_LOAD_IMAGE_OBSERVER_H_
...@@ -217,8 +217,10 @@ Resource* DocumentLoader::StartPreload(Resource::Type type, ...@@ -217,8 +217,10 @@ Resource* DocumentLoader::StartPreload(Resource::Type type,
DCHECK(!client || type == Resource::kCSSStyleSheet); DCHECK(!client || type == Resource::kCSSStyleSheet);
switch (type) { switch (type) {
case Resource::kImage: case Resource::kImage:
if (frame_) if (frame_) {
frame_->MaybeAllowImagePlaceholder(params); frame_->MaybeAllowImagePlaceholder(params);
frame_->MaybeAllowLazyLoadingImage(params);
}
resource = ImageResource::Fetch(params, Fetcher()); resource = ImageResource::Fetch(params, Fetcher());
break; break;
case Resource::kScript: case Resource::kScript:
......
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
#include "third_party/blink/renderer/core/frame/use_counter.h" #include "third_party/blink/renderer/core/frame/use_counter.h"
#include "third_party/blink/renderer/core/html/cross_origin_attribute.h" #include "third_party/blink/renderer/core/html/cross_origin_attribute.h"
#include "third_party/blink/renderer/core/html/html_image_element.h" #include "third_party/blink/renderer/core/html/html_image_element.h"
#include "third_party/blink/renderer/core/html/lazy_load_image_observer.h"
#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
#include "third_party/blink/renderer/core/layout/layout_image.h" #include "third_party/blink/renderer/core/layout/layout_image.h"
#include "third_party/blink/renderer/core/layout/layout_video.h" #include "third_party/blink/renderer/core/layout/layout_video.h"
...@@ -145,7 +146,8 @@ ImageLoader::ImageLoader(Element* element) ...@@ -145,7 +146,8 @@ ImageLoader::ImageLoader(Element* element)
: element_(element), : element_(element),
image_complete_(true), image_complete_(true),
loading_image_document_(false), loading_image_document_(false),
suppress_error_events_(false) { suppress_error_events_(false),
lazy_image_load_state_(LazyImageLoadState::kNone) {
RESOURCE_LOADING_DVLOG(1) << "new ImageLoader " << this; RESOURCE_LOADING_DVLOG(1) << "new ImageLoader " << this;
} }
...@@ -355,8 +357,14 @@ void ImageLoader::UpdateImageState(ImageResourceContent* new_image_content) { ...@@ -355,8 +357,14 @@ void ImageLoader::UpdateImageState(ImageResourceContent* new_image_content) {
if (!new_image_content) { if (!new_image_content) {
image_resource_for_image_document_ = nullptr; image_resource_for_image_document_ = nullptr;
image_complete_ = true; image_complete_ = true;
if (lazy_image_load_state_ == LazyImageLoadState::kDeferred) {
LazyLoadImageObserver::StopMonitoring(GetElement());
lazy_image_load_state_ = LazyImageLoadState::kFullImage;
}
} else { } else {
image_complete_ = false; image_complete_ = false;
if (lazy_image_load_state_ == LazyImageLoadState::kDeferred)
LazyLoadImageObserver::StartMonitoring(GetElement());
} }
delay_until_image_notify_finished_ = nullptr; delay_until_image_notify_finished_ = nullptr;
} }
...@@ -432,8 +440,16 @@ void ImageLoader::DoUpdateFromElement(BypassMainWorldBehavior bypass_behavior, ...@@ -432,8 +440,16 @@ void ImageLoader::DoUpdateFromElement(BypassMainWorldBehavior bypass_behavior,
ConfigureRequest(params, bypass_behavior, *element_, ConfigureRequest(params, bypass_behavior, *element_,
document.GetFrame()->GetClientHintsPreferences()); document.GetFrame()->GetClientHintsPreferences());
if (update_behavior != kUpdateForcedReload && document.GetFrame()) if (update_behavior != kUpdateForcedReload &&
document.GetFrame()->MaybeAllowImagePlaceholder(params); lazy_image_load_state_ == LazyImageLoadState::kNone) {
const auto* frame = document.GetFrame();
frame->MaybeAllowImagePlaceholder(params);
auto* html_image = ToHTMLImageElementOrNull(GetElement());
if (html_image && html_image->ElementCreatedByParser() &&
frame->MaybeAllowLazyLoadingImage(params)) {
lazy_image_load_state_ = LazyImageLoadState::kDeferred;
}
}
new_image_content = ImageResourceContent::Fetch(params, document.Fetcher()); new_image_content = ImageResourceContent::Fetch(params, document.Fetcher());
...@@ -551,6 +567,10 @@ void ImageLoader::UpdateFromElement(UpdateFromElementBehavior update_behavior, ...@@ -551,6 +567,10 @@ void ImageLoader::UpdateFromElement(UpdateFromElementBehavior update_behavior,
image_content_ = nullptr; image_content_ = nullptr;
image_resource_for_image_document_ = nullptr; image_resource_for_image_document_ = nullptr;
delay_until_image_notify_finished_ = nullptr; delay_until_image_notify_finished_ = nullptr;
if (lazy_image_load_state_ != LazyImageLoadState::kNone) {
LazyLoadImageObserver::StopMonitoring(GetElement());
lazy_image_load_state_ = LazyImageLoadState::kNone;
}
} }
// Don't load images for inactive documents. We don't want to slow down the // Don't load images for inactive documents. We don't want to slow down the
...@@ -627,6 +647,23 @@ void ImageLoader::ImageNotifyFinished(ImageResourceContent* resource) { ...@@ -627,6 +647,23 @@ void ImageLoader::ImageNotifyFinished(ImageResourceContent* resource) {
else else
CHECK(!image_complete_); CHECK(!image_complete_);
if (lazy_image_load_state_ == LazyImageLoadState::kDeferred) {
// LazyImages: if a placeholder is loaded, suppress load events and do not
// consider the image as loaded, except for unblocking document load events.
// The final image load (including load events) occurs when the
// non-placeholder image loading (triggered by LoadDeferredImage()) is
// finished.
if (image_content_ && image_content_->GetImage()->IsPlaceholderImage()) {
delay_until_image_notify_finished_ = nullptr;
return;
}
// A placeholder was requested, but the result was an error or a full image.
// In these cases, consider this as the final image and suppress further
// reloading and proceed to the image load completion process below.
LazyLoadImageObserver::StopMonitoring(GetElement());
lazy_image_load_state_ = LazyImageLoadState::kFullImage;
}
image_complete_ = true; image_complete_ = true;
delay_until_image_notify_finished_ = nullptr; delay_until_image_notify_finished_ = nullptr;
...@@ -783,6 +820,14 @@ ScriptPromise ImageLoader::Decode(ScriptState* script_state, ...@@ -783,6 +820,14 @@ ScriptPromise ImageLoader::Decode(ScriptState* script_state,
return request->promise(); return request->promise();
} }
void ImageLoader::LoadDeferredImage(ReferrerPolicy referrer_policy) {
if (lazy_image_load_state_ != LazyImageLoadState::kDeferred)
return;
DCHECK(!image_complete_);
lazy_image_load_state_ = LazyImageLoadState::kFullImage;
UpdateFromElement(kUpdateNormal, referrer_policy);
}
void ImageLoader::ElementDidMoveToNewDocument() { void ImageLoader::ElementDidMoveToNewDocument() {
if (delay_until_do_update_from_element_) { if (delay_until_do_update_from_element_) {
delay_until_do_update_from_element_->DocumentChanged( delay_until_do_update_from_element_->DocumentChanged(
......
...@@ -121,6 +121,8 @@ class CORE_EXPORT ImageLoader : public GarbageCollectedFinalized<ImageLoader>, ...@@ -121,6 +121,8 @@ class CORE_EXPORT ImageLoader : public GarbageCollectedFinalized<ImageLoader>,
ScriptPromise Decode(ScriptState*, ExceptionState&); ScriptPromise Decode(ScriptState*, ExceptionState&);
void LoadDeferredImage(ReferrerPolicy);
protected: protected:
void ImageChanged(ImageResourceContent*, void ImageChanged(ImageResourceContent*,
CanDeferInvalidation, CanDeferInvalidation,
...@@ -132,6 +134,21 @@ class CORE_EXPORT ImageLoader : public GarbageCollectedFinalized<ImageLoader>, ...@@ -132,6 +134,21 @@ class CORE_EXPORT ImageLoader : public GarbageCollectedFinalized<ImageLoader>,
enum class UpdateType { kAsync, kSync }; enum class UpdateType { kAsync, kSync };
// LazyImages: Defer the image load until the image is near the viewport.
// https://docs.google.com/document/d/1jF1eSOhqTEt0L1WBCccGwH9chxLd9d1Ez0zo11obj14
// The state transition is better captured in the below doc.
// https://docs.google.com/document/d/1Ym0EOwyZJmaB5afnCVPu0SFb8EWLBj_facm2fK9kgC0/
enum class LazyImageLoadState {
kNone, // LazyImages not active.
kDeferred, // Placeholder is loading/loaded. Full image load not started.
// Once the placeholder is loaded, document load event is
// unblocked, but image load event is not fired yet.
kFullImage // Full image is loading/loaded, due to element coming near the
// viewport or if a placeholder load actually fetched the full
// image. image_complete_ can differentiate if the fetch is
// complete or not. After the fetch, image load event is fired.
};
// Called from the task or from updateFromElement to initiate the load. // Called from the task or from updateFromElement to initiate the load.
void DoUpdateFromElement(BypassMainWorldBehavior, void DoUpdateFromElement(BypassMainWorldBehavior,
UpdateFromElementBehavior, UpdateFromElementBehavior,
...@@ -212,6 +229,8 @@ class CORE_EXPORT ImageLoader : public GarbageCollectedFinalized<ImageLoader>, ...@@ -212,6 +229,8 @@ class CORE_EXPORT ImageLoader : public GarbageCollectedFinalized<ImageLoader>,
bool loading_image_document_ : 1; bool loading_image_document_ : 1;
bool suppress_error_events_ : 1; bool suppress_error_events_ : 1;
LazyImageLoadState lazy_image_load_state_;
// DecodeRequest represents a single request to the Decode() function. The // DecodeRequest represents a single request to the Decode() function. The
// decode requests have one of the following states: // decode requests have one of the following states:
// //
......
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