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 @@
"label",
"lang",
"language",
"lazyload",
"leftmargin",
"link",
"list",
......
......@@ -30,6 +30,7 @@
#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/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/loader/document_loader.h"
#include "third_party/blink/renderer/core/loader/frame_load_request.h"
......@@ -337,6 +338,16 @@ bool HTMLFrameOwnerElement::LoadOrRedirectSubframe(
const KURL& url,
const AtomicString& frame_name,
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();
if (ContentFrame()) {
......@@ -380,25 +391,27 @@ bool HTMLFrameOwnerElement::LoadOrRedirectSubframe(
if ((RuntimeEnabledFeatures::LazyFrameLoadingEnabled() ||
RuntimeEnabledFeatures::LazyFrameVisibleLoadTimeMetricsEnabled()) &&
should_lazy_load_children_ &&
!lazy_load_frame_observer_ &&
// Only http:// or https:// URLs are eligible for lazy loading, excluding
// URLs like invalid or empty URLs, "about:blank", local file URLs, etc.
// that it doesn't make sense to lazily load.
url.ProtocolIsInHTTPFamily() &&
// Disallow lazy loading if javascript in the embedding document would be
// able to access the contents of the frame, since in those cases
// deferring the frame could break the page. Note that this check does not
// take any possible redirects of |url| into account.
(EqualIgnoringASCIICase(FastGetAttribute(HTMLNames::lazyloadAttr),
"on") ||
(should_lazy_load_children_ &&
// Disallow lazy loading by default if javascript in the embedding
// document would be able to access the contents of the frame, since in
// those cases deferring the frame could break the page. Note that this
// check does not take any possible redirects of |url| into account.
!GetDocument().GetSecurityOrigin()->CanAccess(
SecurityOrigin::Create(url).get())) {
// Don't lazy load subresources inside a lazily loaded frame. 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.
SecurityOrigin::Create(url).get())))) {
// By default, avoid deferring subresources inside a lazily loaded frame.
// 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;
DCHECK(!lazy_load_frame_observer_);
lazy_load_frame_observer_ = new LazyLoadFrameObserver(*this);
if (RuntimeEnabledFeatures::LazyFrameVisibleLoadTimeMetricsEnabled())
......@@ -427,6 +440,21 @@ bool HTMLFrameOwnerElement::ShouldLazyLoadChildren() const {
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) {
visitor->Trace(content_frame_);
visitor->Trace(embedded_content_view_);
......
......@@ -118,6 +118,8 @@ class CORE_EXPORT HTMLFrameOwnerElement : public HTMLElement,
void CancelPendingLazyLoad();
void ParseAttribute(const AttributeModificationParams&) override;
void Trace(blink::Visitor*) override;
protected:
......
......@@ -77,13 +77,28 @@ int GetLazyFrameLoadingViewportDistanceThresholdPx(const Document& document) {
} // 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)
: element_(&element) {}
LazyLoadFrameObserver::~LazyLoadFrameObserver() = default;
void LazyLoadFrameObserver::DeferLoadUntilNearViewport(
const ResourceRequest& resource_request,
WebFrameLoadType frame_load_type) {
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;
lazy_load_intersection_observer_ = IntersectionObserver::Create(
......@@ -92,13 +107,14 @@ void LazyLoadFrameObserver::DeferLoadUntilNearViewport(
kFixed)},
{std::numeric_limits<float>::min()}, &element_->GetDocument(),
WTF::BindRepeating(&LazyLoadFrameObserver::LoadIfHiddenOrNearViewport,
WrapWeakPersistent(this), resource_request,
frame_load_type));
WrapWeakPersistent(this)));
lazy_load_intersection_observer_->observe(element_);
}
void LazyLoadFrameObserver::CancelPendingLazyLoad() {
lazy_load_request_info_.reset();
if (!lazy_load_intersection_observer_)
return;
lazy_load_intersection_observer_->disconnect();
......@@ -106,8 +122,6 @@ void LazyLoadFrameObserver::CancelPendingLazyLoad() {
}
void LazyLoadFrameObserver::LoadIfHiddenOrNearViewport(
const ResourceRequest& resource_request,
WebFrameLoadType frame_load_type,
const HeapVector<Member<IntersectionObserverEntry>>& entries) {
DCHECK(!entries.IsEmpty());
DCHECK_EQ(element_, entries.back()->target());
......@@ -122,6 +136,13 @@ void LazyLoadFrameObserver::LoadIfHiddenOrNearViewport(
return;
}
LoadImmediately();
}
void LazyLoadFrameObserver::LoadImmediately() {
DCHECK(IsLazyLoadPending());
DCHECK(lazy_load_request_info_);
if (was_recorded_as_deferred_) {
DCHECK(element_->GetDocument().GetFrame());
DCHECK(element_->GetDocument().GetFrame()->Client());
......@@ -134,18 +155,23 @@ void LazyLoadFrameObserver::LoadIfHiddenOrNearViewport(
->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
// pending lazy load should have been already been cancelled in
// DisconnectContentFrame() if the content frame changes.
DCHECK(element_->ContentFrame());
// Note that calling FrameLoader::Load() causes the
// |lazy_load_intersection_observer| to be disconnected.
// Note that calling FrameLoader::StartNavigation() causes the
// |lazy_load_intersection_observer_| to be disconnected.
ToLocalFrame(element_->ContentFrame())
->Loader()
.StartNavigation(
FrameLoadRequest(&element_->GetDocument(), resource_request),
frame_load_type);
.StartNavigation(FrameLoadRequest(&element_->GetDocument(),
scoped_request_info->resource_request),
scoped_request_info->frame_load_type);
DCHECK(!IsLazyLoadPending());
}
void LazyLoadFrameObserver::StartTrackingVisibilityMetrics() {
......
......@@ -5,6 +5,8 @@
#ifndef 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/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/heap/heap_allocator.h"
......@@ -19,7 +21,8 @@ class HTMLFrameOwnerElement;
class ResourceRequest;
class Visitor;
class LazyLoadFrameObserver : public GarbageCollected<LazyLoadFrameObserver> {
class LazyLoadFrameObserver
: public GarbageCollectedFinalized<LazyLoadFrameObserver> {
public:
// This enum is logged to histograms, so values should not be reordered or
// reused, and it must match the corresponding enum
......@@ -40,6 +43,7 @@ class LazyLoadFrameObserver : public GarbageCollected<LazyLoadFrameObserver> {
};
explicit LazyLoadFrameObserver(HTMLFrameOwnerElement&);
~LazyLoadFrameObserver();
void DeferLoadUntilNearViewport(const ResourceRequest&, WebFrameLoadType);
bool IsLazyLoadPending() const { return lazy_load_intersection_observer_; }
......@@ -48,12 +52,14 @@ class LazyLoadFrameObserver : public GarbageCollected<LazyLoadFrameObserver> {
void StartTrackingVisibilityMetrics();
void RecordMetricsOnLoadFinished();
void LoadImmediately();
void Trace(blink::Visitor*);
private:
struct LazyLoadRequestInfo;
void LoadIfHiddenOrNearViewport(
const ResourceRequest&,
WebFrameLoadType,
const HeapVector<Member<IntersectionObserverEntry>>&);
void RecordMetricsOnVisibilityChanged(
......@@ -68,6 +74,10 @@ class LazyLoadFrameObserver : public GarbageCollected<LazyLoadFrameObserver> {
// the viewport.
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
// IntersectionObserver instead of just an ElementVisibilityObserver so that
// 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