Commit b5278c36 authored by Scott Little's avatar Scott Little Committed by Commit Bot

LazyLoad: Implement support for "lazyload" attribute on frames.

This CL implements support for the "lazyload" attribute on frames,
according to https://github.com/whatwg/html/pull/3752, and as part of
the LazyLoad feature. The accepted values are:

"on", which causes the browser to lazily load a frame even if it's
same-origin or nested inside another lazyloaded frame,

"off", which causes the browser to avoid lazily loading this frame or
any of it's children (unless those children are marked with
lazyload="on"),

"auto", which activates the default behavior, and is therefore not
explicitly handled in code.

Bug: 873358
Change-Id: I2fde65adb15216260291b08e39888a2363f44d4a
Reviewed-on: https://chromium-review.googlesource.com/1176293
Commit-Queue: Scott Little <sclittle@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#583841}
parent 8e225510
...@@ -109,6 +109,7 @@ ...@@ -109,6 +109,7 @@
"label", "label",
"lang", "lang",
"language", "language",
"lazyload",
"leftmargin", "leftmargin",
"link", "link",
"list", "list",
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/remote_frame_view.h" #include "third_party/blink/renderer/core/frame/remote_frame_view.h"
#include "third_party/blink/renderer/core/html/lazy_load_frame_observer.h" #include "third_party/blink/renderer/core/html/lazy_load_frame_observer.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/layout/layout_embedded_content.h" #include "third_party/blink/renderer/core/layout/layout_embedded_content.h"
#include "third_party/blink/renderer/core/loader/document_loader.h" #include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/loader/frame_load_request.h" #include "third_party/blink/renderer/core/loader/frame_load_request.h"
...@@ -337,6 +338,16 @@ bool HTMLFrameOwnerElement::LoadOrRedirectSubframe( ...@@ -337,6 +338,16 @@ bool HTMLFrameOwnerElement::LoadOrRedirectSubframe(
const KURL& url, const KURL& url,
const AtomicString& frame_name, const AtomicString& frame_name,
bool replace_current_item) { bool replace_current_item) {
// Update the |should_lazy_load_children_| value according to the "lazyload"
// attribute immediately, so that it still gets respected even if the "src"
// attribute gets parsed in ParseAttribute() before the "lazyload" attribute
// does.
if (should_lazy_load_children_ &&
EqualIgnoringASCIICase(FastGetAttribute(HTMLNames::lazyloadAttr),
"off")) {
should_lazy_load_children_ = false;
}
UpdateContainerPolicy(); UpdateContainerPolicy();
if (ContentFrame()) { if (ContentFrame()) {
...@@ -380,25 +391,27 @@ bool HTMLFrameOwnerElement::LoadOrRedirectSubframe( ...@@ -380,25 +391,27 @@ bool HTMLFrameOwnerElement::LoadOrRedirectSubframe(
if ((RuntimeEnabledFeatures::LazyFrameLoadingEnabled() || if ((RuntimeEnabledFeatures::LazyFrameLoadingEnabled() ||
RuntimeEnabledFeatures::LazyFrameVisibleLoadTimeMetricsEnabled()) && RuntimeEnabledFeatures::LazyFrameVisibleLoadTimeMetricsEnabled()) &&
should_lazy_load_children_ && !lazy_load_frame_observer_ &&
// Only http:// or https:// URLs are eligible for lazy loading, excluding // Only http:// or https:// URLs are eligible for lazy loading, excluding
// URLs like invalid or empty URLs, "about:blank", local file URLs, etc. // URLs like invalid or empty URLs, "about:blank", local file URLs, etc.
// that it doesn't make sense to lazily load. // that it doesn't make sense to lazily load.
url.ProtocolIsInHTTPFamily() && url.ProtocolIsInHTTPFamily() &&
// Disallow lazy loading if javascript in the embedding document would be (EqualIgnoringASCIICase(FastGetAttribute(HTMLNames::lazyloadAttr),
// able to access the contents of the frame, since in those cases "on") ||
// deferring the frame could break the page. Note that this check does not (should_lazy_load_children_ &&
// take any possible redirects of |url| into account. // Disallow lazy loading by default if javascript in the embedding
!GetDocument().GetSecurityOrigin()->CanAccess( // document would be able to access the contents of the frame, since in
SecurityOrigin::Create(url).get())) { // those cases deferring the frame could break the page. Note that this
// Don't lazy load subresources inside a lazily loaded frame. This will make // check does not take any possible redirects of |url| into account.
// it possible for subresources in hidden frames to load that will !GetDocument().GetSecurityOrigin()->CanAccess(
// never be visible, as well as make it so that deferred frames that have SecurityOrigin::Create(url).get())))) {
// multiple layers of iframes inside them can load faster once they're near // By default, avoid deferring subresources inside a lazily loaded frame.
// the viewport or visible. // This will make it possible for subresources in hidden frames to load that
// will never be visible, as well as make it so that deferred frames that
// have multiple layers of iframes inside them can load faster once they're
// near the viewport or visible.
should_lazy_load_children_ = false; should_lazy_load_children_ = false;
DCHECK(!lazy_load_frame_observer_);
lazy_load_frame_observer_ = new LazyLoadFrameObserver(*this); lazy_load_frame_observer_ = new LazyLoadFrameObserver(*this);
if (RuntimeEnabledFeatures::LazyFrameVisibleLoadTimeMetricsEnabled()) if (RuntimeEnabledFeatures::LazyFrameVisibleLoadTimeMetricsEnabled())
...@@ -427,6 +440,21 @@ bool HTMLFrameOwnerElement::ShouldLazyLoadChildren() const { ...@@ -427,6 +440,21 @@ bool HTMLFrameOwnerElement::ShouldLazyLoadChildren() const {
return should_lazy_load_children_; return should_lazy_load_children_;
} }
void HTMLFrameOwnerElement::ParseAttribute(
const AttributeModificationParams& params) {
if (params.name == HTMLNames::lazyloadAttr) {
if (EqualIgnoringASCIICase(params.new_value, "off")) {
should_lazy_load_children_ = false;
if (lazy_load_frame_observer_ &&
lazy_load_frame_observer_->IsLazyLoadPending()) {
lazy_load_frame_observer_->LoadImmediately();
}
}
} else {
HTMLElement::ParseAttribute(params);
}
}
void HTMLFrameOwnerElement::Trace(blink::Visitor* visitor) { void HTMLFrameOwnerElement::Trace(blink::Visitor* visitor) {
visitor->Trace(content_frame_); visitor->Trace(content_frame_);
visitor->Trace(embedded_content_view_); visitor->Trace(embedded_content_view_);
......
...@@ -118,6 +118,8 @@ class CORE_EXPORT HTMLFrameOwnerElement : public HTMLElement, ...@@ -118,6 +118,8 @@ class CORE_EXPORT HTMLFrameOwnerElement : public HTMLElement,
void CancelPendingLazyLoad(); void CancelPendingLazyLoad();
void ParseAttribute(const AttributeModificationParams&) override;
void Trace(blink::Visitor*) override; void Trace(blink::Visitor*) override;
protected: protected:
......
...@@ -77,13 +77,28 @@ int GetLazyFrameLoadingViewportDistanceThresholdPx(const Document& document) { ...@@ -77,13 +77,28 @@ int GetLazyFrameLoadingViewportDistanceThresholdPx(const Document& document) {
} // namespace } // namespace
struct LazyLoadFrameObserver::LazyLoadRequestInfo {
LazyLoadRequestInfo(const ResourceRequest& resource_request,
WebFrameLoadType frame_load_type)
: resource_request(resource_request), frame_load_type(frame_load_type) {}
const ResourceRequest resource_request;
const WebFrameLoadType frame_load_type;
};
LazyLoadFrameObserver::LazyLoadFrameObserver(HTMLFrameOwnerElement& element) LazyLoadFrameObserver::LazyLoadFrameObserver(HTMLFrameOwnerElement& element)
: element_(&element) {} : element_(&element) {}
LazyLoadFrameObserver::~LazyLoadFrameObserver() = default;
void LazyLoadFrameObserver::DeferLoadUntilNearViewport( void LazyLoadFrameObserver::DeferLoadUntilNearViewport(
const ResourceRequest& resource_request, const ResourceRequest& resource_request,
WebFrameLoadType frame_load_type) { WebFrameLoadType frame_load_type) {
DCHECK(!lazy_load_intersection_observer_); DCHECK(!lazy_load_intersection_observer_);
DCHECK(!lazy_load_request_info_);
lazy_load_request_info_ =
std::make_unique<LazyLoadRequestInfo>(resource_request, frame_load_type);
was_recorded_as_deferred_ = false; was_recorded_as_deferred_ = false;
lazy_load_intersection_observer_ = IntersectionObserver::Create( lazy_load_intersection_observer_ = IntersectionObserver::Create(
...@@ -92,13 +107,14 @@ void LazyLoadFrameObserver::DeferLoadUntilNearViewport( ...@@ -92,13 +107,14 @@ void LazyLoadFrameObserver::DeferLoadUntilNearViewport(
kFixed)}, kFixed)},
{std::numeric_limits<float>::min()}, &element_->GetDocument(), {std::numeric_limits<float>::min()}, &element_->GetDocument(),
WTF::BindRepeating(&LazyLoadFrameObserver::LoadIfHiddenOrNearViewport, WTF::BindRepeating(&LazyLoadFrameObserver::LoadIfHiddenOrNearViewport,
WrapWeakPersistent(this), resource_request, WrapWeakPersistent(this)));
frame_load_type));
lazy_load_intersection_observer_->observe(element_); lazy_load_intersection_observer_->observe(element_);
} }
void LazyLoadFrameObserver::CancelPendingLazyLoad() { void LazyLoadFrameObserver::CancelPendingLazyLoad() {
lazy_load_request_info_.reset();
if (!lazy_load_intersection_observer_) if (!lazy_load_intersection_observer_)
return; return;
lazy_load_intersection_observer_->disconnect(); lazy_load_intersection_observer_->disconnect();
...@@ -106,8 +122,6 @@ void LazyLoadFrameObserver::CancelPendingLazyLoad() { ...@@ -106,8 +122,6 @@ void LazyLoadFrameObserver::CancelPendingLazyLoad() {
} }
void LazyLoadFrameObserver::LoadIfHiddenOrNearViewport( void LazyLoadFrameObserver::LoadIfHiddenOrNearViewport(
const ResourceRequest& resource_request,
WebFrameLoadType frame_load_type,
const HeapVector<Member<IntersectionObserverEntry>>& entries) { const HeapVector<Member<IntersectionObserverEntry>>& entries) {
DCHECK(!entries.IsEmpty()); DCHECK(!entries.IsEmpty());
DCHECK_EQ(element_, entries.back()->target()); DCHECK_EQ(element_, entries.back()->target());
...@@ -122,6 +136,13 @@ void LazyLoadFrameObserver::LoadIfHiddenOrNearViewport( ...@@ -122,6 +136,13 @@ void LazyLoadFrameObserver::LoadIfHiddenOrNearViewport(
return; return;
} }
LoadImmediately();
}
void LazyLoadFrameObserver::LoadImmediately() {
DCHECK(IsLazyLoadPending());
DCHECK(lazy_load_request_info_);
if (was_recorded_as_deferred_) { if (was_recorded_as_deferred_) {
DCHECK(element_->GetDocument().GetFrame()); DCHECK(element_->GetDocument().GetFrame());
DCHECK(element_->GetDocument().GetFrame()->Client()); DCHECK(element_->GetDocument().GetFrame()->Client());
...@@ -134,18 +155,23 @@ void LazyLoadFrameObserver::LoadIfHiddenOrNearViewport( ...@@ -134,18 +155,23 @@ void LazyLoadFrameObserver::LoadIfHiddenOrNearViewport(
->GetEffectiveConnectionType()); ->GetEffectiveConnectionType());
} }
std::unique_ptr<LazyLoadRequestInfo> scoped_request_info =
std::move(lazy_load_request_info_);
// The content frame of the element should not have changed, since any // The content frame of the element should not have changed, since any
// pending lazy load should have been already been cancelled in // pending lazy load should have been already been cancelled in
// DisconnectContentFrame() if the content frame changes. // DisconnectContentFrame() if the content frame changes.
DCHECK(element_->ContentFrame()); DCHECK(element_->ContentFrame());
// Note that calling FrameLoader::Load() causes the // Note that calling FrameLoader::StartNavigation() causes the
// |lazy_load_intersection_observer| to be disconnected. // |lazy_load_intersection_observer_| to be disconnected.
ToLocalFrame(element_->ContentFrame()) ToLocalFrame(element_->ContentFrame())
->Loader() ->Loader()
.StartNavigation( .StartNavigation(FrameLoadRequest(&element_->GetDocument(),
FrameLoadRequest(&element_->GetDocument(), resource_request), scoped_request_info->resource_request),
frame_load_type); scoped_request_info->frame_load_type);
DCHECK(!IsLazyLoadPending());
} }
void LazyLoadFrameObserver::StartTrackingVisibilityMetrics() { void LazyLoadFrameObserver::StartTrackingVisibilityMetrics() {
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_LAZY_LOAD_FRAME_OBSERVER_H_ #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_HTML_LAZY_LOAD_FRAME_OBSERVER_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_LAZY_LOAD_FRAME_OBSERVER_H_ #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_LAZY_LOAD_FRAME_OBSERVER_H_
#include <memory>
#include "third_party/blink/public/web/web_frame_load_type.h" #include "third_party/blink/public/web/web_frame_load_type.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.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/heap_allocator.h"
...@@ -19,7 +21,8 @@ class HTMLFrameOwnerElement; ...@@ -19,7 +21,8 @@ class HTMLFrameOwnerElement;
class ResourceRequest; class ResourceRequest;
class Visitor; class Visitor;
class LazyLoadFrameObserver : public GarbageCollected<LazyLoadFrameObserver> { class LazyLoadFrameObserver
: public GarbageCollectedFinalized<LazyLoadFrameObserver> {
public: public:
// This enum is logged to histograms, so values should not be reordered or // This enum is logged to histograms, so values should not be reordered or
// reused, and it must match the corresponding enum // reused, and it must match the corresponding enum
...@@ -40,6 +43,7 @@ class LazyLoadFrameObserver : public GarbageCollected<LazyLoadFrameObserver> { ...@@ -40,6 +43,7 @@ class LazyLoadFrameObserver : public GarbageCollected<LazyLoadFrameObserver> {
}; };
explicit LazyLoadFrameObserver(HTMLFrameOwnerElement&); explicit LazyLoadFrameObserver(HTMLFrameOwnerElement&);
~LazyLoadFrameObserver();
void DeferLoadUntilNearViewport(const ResourceRequest&, WebFrameLoadType); void DeferLoadUntilNearViewport(const ResourceRequest&, WebFrameLoadType);
bool IsLazyLoadPending() const { return lazy_load_intersection_observer_; } bool IsLazyLoadPending() const { return lazy_load_intersection_observer_; }
...@@ -48,12 +52,14 @@ class LazyLoadFrameObserver : public GarbageCollected<LazyLoadFrameObserver> { ...@@ -48,12 +52,14 @@ class LazyLoadFrameObserver : public GarbageCollected<LazyLoadFrameObserver> {
void StartTrackingVisibilityMetrics(); void StartTrackingVisibilityMetrics();
void RecordMetricsOnLoadFinished(); void RecordMetricsOnLoadFinished();
void LoadImmediately();
void Trace(blink::Visitor*); void Trace(blink::Visitor*);
private: private:
struct LazyLoadRequestInfo;
void LoadIfHiddenOrNearViewport( void LoadIfHiddenOrNearViewport(
const ResourceRequest&,
WebFrameLoadType,
const HeapVector<Member<IntersectionObserverEntry>>&); const HeapVector<Member<IntersectionObserverEntry>>&);
void RecordMetricsOnVisibilityChanged( void RecordMetricsOnVisibilityChanged(
...@@ -68,6 +74,10 @@ class LazyLoadFrameObserver : public GarbageCollected<LazyLoadFrameObserver> { ...@@ -68,6 +74,10 @@ class LazyLoadFrameObserver : public GarbageCollected<LazyLoadFrameObserver> {
// the viewport. // the viewport.
Member<IntersectionObserver> lazy_load_intersection_observer_; Member<IntersectionObserver> lazy_load_intersection_observer_;
// Keeps track of the resource request and other info needed to load in the
// deferred frame. This is only non-null if there's a lazy load pending.
std::unique_ptr<LazyLoadRequestInfo> lazy_load_request_info_;
// Used to record visibility-related metrics related to lazy load. This is an // Used to record visibility-related metrics related to lazy load. This is an
// IntersectionObserver instead of just an ElementVisibilityObserver so that // IntersectionObserver instead of just an ElementVisibilityObserver so that
// hidden frames can be detected in order to avoid recording metrics for them. // hidden frames can be detected in order to avoid recording metrics for them.
......
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