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.
......
...@@ -8,10 +8,13 @@ ...@@ -8,10 +8,13 @@
#include <memory> #include <memory>
#include <tuple> #include <tuple>
#include "base/optional.h"
#include "third_party/blink/public/platform/web_effective_connection_type.h" #include "third_party/blink/public/platform/web_effective_connection_type.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/exported/web_view_impl.h" #include "third_party/blink/renderer/core/exported/web_view_impl.h"
#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/settings.h" #include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/testing/sim/sim_compositor.h" #include "third_party/blink/renderer/core/testing/sim/sim_compositor.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h" #include "third_party/blink/renderer/core/testing/sim/sim_request.h"
...@@ -719,6 +722,353 @@ TEST_P(LazyLoadFramesTest, JavascriptStringFrameUrl) { ...@@ -719,6 +722,353 @@ TEST_P(LazyLoadFramesTest, JavascriptStringFrameUrl) {
"Blink.LazyLoad.CrossOriginFrames.VisibleAfterBeingDeferred", 0); "Blink.LazyLoad.CrossOriginFrames.VisibleAfterBeingDeferred", 0);
} }
TEST_P(LazyLoadFramesTest, CrossOriginFrameFarFromViewportWithLazyLoadAttrOff) {
SimRequest main_resource("https://example.com/", "text/html");
SimRequest child_frame_resource("https://crossorigin.com/subframe.html",
"text/html");
LoadURL("https://example.com/");
main_resource.Complete(String::Format(
R"HTML(
<body onload='console.log("main body onload");'>
<div style='height: %dpx;'></div>
<iframe src='https://crossorigin.com/subframe.html'
style='width: 200px; height: 200px;' lazyload='off'
onload='console.log("child frame element onload");'></iframe>
</body>)HTML",
kViewportHeight + GetLoadingDistanceThreshold() + 100));
Compositor().BeginFrame();
test::RunPendingTasks();
child_frame_resource.Complete("");
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("child frame element onload"));
ExpectVisibleLoadTimeHistogramSamplesIfApplicable(0, 0);
histogram_tester()->ExpectTotalCount(
"Blink.VisibleBeforeLoaded.LazyLoadEligibleFrames.BelowTheFold", 0);
for (const auto& pair : kInitialDeferralActionHistogramNames)
histogram_tester()->ExpectTotalCount(pair.second, 0);
histogram_tester()->ExpectTotalCount(
"Blink.LazyLoad.CrossOriginFrames.LoadStartedAfterBeingDeferred", 0);
histogram_tester()->ExpectTotalCount(
"Blink.LazyLoad.CrossOriginFrames.VisibleAfterBeingDeferred", 0);
}
TEST_P(LazyLoadFramesTest,
LoadSameOriginFrameFarFromViewportWithLazyLoadAttributeOn) {
SimRequest main_resource("https://example.com/", "text/html");
base::Optional<SimRequest> child_frame_resource;
if (!RuntimeEnabledFeatures::LazyFrameLoadingEnabled()) {
// This SimRequest needs to be created now if the frame won't actually be
// lazily loaded. Otherwise, it'll be defined later to ensure that the
// subframe resource isn't requested until the page is scrolled down.
child_frame_resource.emplace("https://example.com/subframe.html",
"text/html");
}
LoadURL("https://example.com/");
main_resource.Complete(String::Format(
R"HTML(
<body onload='console.log("main body onload");'>
<div style='height: %dpx;'></div>
<iframe src='https://example.com/subframe.html'
style='width: 400px; height: 400px;' lazyload='on'
onload='console.log("child frame element onload");'></iframe>
</body>)HTML",
kViewportHeight + GetLoadingDistanceThreshold() + 100));
Compositor().BeginFrame();
test::RunPendingTasks();
// If the child frame is being lazy loaded, then the body's load event
// should have already fired.
EXPECT_EQ(RuntimeEnabledFeatures::LazyFrameLoadingEnabled(),
ConsoleMessages().Contains("main body onload"));
EXPECT_FALSE(ConsoleMessages().Contains("child frame element onload"));
ExpectVisibleLoadTimeHistogramSamplesIfApplicable(0, 0);
ExpectInitialDeferralActionHistogramSamplesIfApplicable(
LazyLoadFrameObserver::FrameInitialDeferralAction::kDeferred, 1);
histogram_tester()->ExpectTotalCount(
"Blink.LazyLoad.CrossOriginFrames.LoadStartedAfterBeingDeferred", 0);
histogram_tester()->ExpectTotalCount(
"Blink.LazyLoad.CrossOriginFrames.VisibleAfterBeingDeferred", 0);
if (!child_frame_resource) {
child_frame_resource.emplace("https://example.com/subframe.html",
"text/html");
}
if (RuntimeEnabledFeatures::LazyFrameLoadingEnabled()) {
// If LazyFrameLoading is enabled, then scroll down near the child frame to
// cause the child frame to start loading.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 150), kProgrammaticScroll);
Compositor().BeginFrame();
test::RunPendingTasks();
}
EXPECT_FALSE(ConsoleMessages().Contains("child frame element onload"));
ExpectVisibleLoadTimeHistogramSamplesIfApplicable(0, 0);
ExpectInitialDeferralActionHistogramSamplesIfApplicable(
LazyLoadFrameObserver::FrameInitialDeferralAction::kDeferred, 1);
ExpectLoadStartedAfterDeferredSamplesIfApplicable(1);
histogram_tester()->ExpectTotalCount(
"Blink.LazyLoad.CrossOriginFrames.VisibleAfterBeingDeferred", 0);
child_frame_resource->Complete("");
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("child frame element onload"));
ExpectVisibleLoadTimeHistogramSamplesIfApplicable(0, 0);
// Scroll down so that the child frame is visible.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, GetLoadingDistanceThreshold() + 150),
kProgrammaticScroll);
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("child frame element onload"));
ExpectVisibleLoadTimeHistogramSamplesIfApplicable(0, 1);
histogram_tester()->ExpectTotalCount(
"Blink.VisibleBeforeLoaded.LazyLoadEligibleFrames.BelowTheFold", 0);
ExpectInitialDeferralActionHistogramSamplesIfApplicable(
LazyLoadFrameObserver::FrameInitialDeferralAction::kDeferred, 1);
ExpectLoadStartedAfterDeferredSamplesIfApplicable(1);
ExpectVisibleAfterDeferredSamplesIfApplicable(1);
}
TEST_P(LazyLoadFramesTest,
LoadCrossOriginFrameFarFromViewportThenSetLazyLoadAttributeOff) {
SimRequest main_resource("https://example.com/", "text/html");
base::Optional<SimRequest> child_frame_resource;
if (!RuntimeEnabledFeatures::LazyFrameLoadingEnabled()) {
// This SimRequest needs to be created now if the frame won't actually be
// lazily loaded. Otherwise, it'll be defined later to ensure that the
// subframe resource isn't requested until the page is scrolled down.
child_frame_resource.emplace("https://crossorigin.com/subframe.html",
"text/html");
}
LoadURL("https://example.com/");
main_resource.Complete(String::Format(
R"HTML(
<body onload='console.log("main body onload");'>
<div style='height: %dpx;'></div>
<iframe id='child_frame' src='https://crossorigin.com/subframe.html'
style='width: 400px; height: 400px;'
onload='console.log("child frame element onload");'></iframe>
</body>)HTML",
kViewportHeight + GetLoadingDistanceThreshold() + 100));
Compositor().BeginFrame();
test::RunPendingTasks();
// If the child frame is being lazy loaded, then the body's load event
// should have already fired.
EXPECT_EQ(RuntimeEnabledFeatures::LazyFrameLoadingEnabled(),
ConsoleMessages().Contains("main body onload"));
EXPECT_FALSE(ConsoleMessages().Contains("child frame element onload"));
if (!child_frame_resource) {
child_frame_resource.emplace("https://crossorigin.com/subframe.html",
"text/html");
}
Element* child_frame_element = GetDocument().getElementById("child_frame");
ASSERT_TRUE(child_frame_element);
child_frame_element->setAttribute(HTMLNames::lazyloadAttr, "off");
ExpectInitialDeferralActionHistogramSamplesIfApplicable(
LazyLoadFrameObserver::FrameInitialDeferralAction::kDeferred, 1);
ExpectLoadStartedAfterDeferredSamplesIfApplicable(1);
histogram_tester()->ExpectTotalCount(
"Blink.LazyLoad.CrossOriginFrames.VisibleAfterBeingDeferred", 0);
child_frame_resource->Complete("");
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("child frame element onload"));
ExpectVisibleLoadTimeHistogramSamplesIfApplicable(0, 0);
histogram_tester()->ExpectTotalCount(
"Blink.VisibleBeforeLoaded.LazyLoadEligibleFrames.BelowTheFold", 0);
ExpectInitialDeferralActionHistogramSamplesIfApplicable(
LazyLoadFrameObserver::FrameInitialDeferralAction::kDeferred, 1);
ExpectLoadStartedAfterDeferredSamplesIfApplicable(1);
histogram_tester()->ExpectTotalCount(
"Blink.LazyLoad.CrossOriginFrames.VisibleAfterBeingDeferred", 0);
}
TEST_P(LazyLoadFramesTest,
NestedFrameWithLazyLoadAttributeOnInFrameWithNoLazyLoadAttribute) {
std::unique_ptr<SimRequest> child_frame_resource =
LoadPageWithCrossOriginFrameFarFromViewport();
if (RuntimeEnabledFeatures::LazyFrameLoadingEnabled()) {
// If LazyFrameLoading is enabled, then scroll down near the child frame to
// cause the child frame to start loading.
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 150), kProgrammaticScroll);
Compositor().BeginFrame();
test::RunPendingTasks();
}
// There's another nested cross origin iframe inside the first child frame,
// even further down such that it's not near the viewport. If LazyLoad is
// enabled, it should be deferred even though it's nested inside a frame that
// was previously deferred, because it has the attribute lazyload=on.
base::Optional<SimRequest> nested_frame_resource;
if (!RuntimeEnabledFeatures::LazyFrameLoadingEnabled())
nested_frame_resource.emplace("https://test.com/", "text/html");
child_frame_resource->Complete(
String::Format("<div style='height: %dpx;'></div>"
"<iframe src='https://test.com/' lazyload='on'"
" style='width: 200px; height: 200px;'>"
"</iframe>",
kViewportHeight + GetLoadingDistanceThreshold() + 100));
Compositor().BeginFrame();
test::RunPendingTasks();
if (!RuntimeEnabledFeatures::LazyFrameLoadingEnabled()) {
nested_frame_resource->Complete("");
Compositor().BeginFrame();
test::RunPendingTasks();
}
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("child frame element onload"));
}
TEST_P(LazyLoadFramesTest,
NestedFrameWithLazyLoadAttributeOnInFrameWithLazyLoadAttributeOff) {
SimRequest main_resource("https://example.com/", "text/html");
SimRequest child_frame_resource("https://crossorigin.com/subframe.html",
"text/html");
LoadURL("https://example.com/");
main_resource.Complete(String::Format(
R"HTML(
<body onload='console.log("main body onload");'>
<div style='height: %dpx;'></div>
<iframe src='https://crossorigin.com/subframe.html'
style='width: 200px; height: 200px;' lazyload='off'
onload='console.log("child frame element onload");'></iframe>
</body>)HTML",
kViewportHeight + GetLoadingDistanceThreshold() + 100));
Compositor().BeginFrame();
test::RunPendingTasks();
// There's another nested cross origin iframe inside the first child frame,
// even further down such that it's not near the viewport. If LazyLoad is
// enabled, it should be deferred because it has the attribute lazyload=on,
// even though it's nested inside a frame that has the attribute lazyload=off.
base::Optional<SimRequest> nested_frame_resource;
if (!RuntimeEnabledFeatures::LazyFrameLoadingEnabled())
nested_frame_resource.emplace("https://test.com/", "text/html");
child_frame_resource.Complete(
String::Format("<div style='height: %dpx;'></div>"
"<iframe src='https://test.com/' lazyload='on'"
" style='width: 200px; height: 200px;'>"
"</iframe>",
kViewportHeight + GetLoadingDistanceThreshold() + 100));
Compositor().BeginFrame();
test::RunPendingTasks();
if (!RuntimeEnabledFeatures::LazyFrameLoadingEnabled()) {
nested_frame_resource->Complete("");
Compositor().BeginFrame();
test::RunPendingTasks();
}
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("child frame element onload"));
}
TEST_P(LazyLoadFramesTest,
NestedFrameWithLazyLoadAttributeOffInFrameWithLazyLoadAttributeOff) {
SimRequest main_resource("https://example.com/", "text/html");
SimRequest child_frame_resource("https://crossorigin.com/subframe.html",
"text/html");
LoadURL("https://example.com/");
main_resource.Complete(String::Format(
R"HTML(
<body onload='console.log("main body onload");'>
<div style='height: %dpx;'></div>
<iframe src='https://crossorigin.com/subframe.html'
style='width: 200px; height: 200px;' lazyload='off'
onload='console.log("child frame element onload");'></iframe>
</body>)HTML",
kViewportHeight + GetLoadingDistanceThreshold() + 100));
Compositor().BeginFrame();
test::RunPendingTasks();
// There's another nested cross origin iframe inside the first child frame,
// even further down such that it's not near the viewport. Since it has the
// attribute lazyload=off, it shouldn't be deferred. Note that this also
// matches the default behavior that would happen if the lazyload attribute
// was omitted on the nested iframe entirely.
SimRequest nested_frame_resource("https://test.com/", "text/html");
child_frame_resource.Complete(
String::Format("<div style='height: %dpx;'></div>"
"<iframe src='https://test.com/' lazyload='off'"
" style='width: 200px; height: 200px;'>"
"</iframe>",
kViewportHeight + GetLoadingDistanceThreshold() + 100));
Compositor().BeginFrame();
test::RunPendingTasks();
nested_frame_resource.Complete("");
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_TRUE(ConsoleMessages().Contains("main body onload"));
EXPECT_TRUE(ConsoleMessages().Contains("child frame element onload"));
}
INSTANTIATE_TEST_CASE_P( INSTANTIATE_TEST_CASE_P(
LazyFrameLoading, LazyFrameLoading,
LazyLoadFramesTest, LazyLoadFramesTest,
......
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