blob: 6312cb3071bd08a40ab677c435c80a07ac174157 [file] [log] [blame]
// Copyright 2019 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/loader/lazy_image_helper.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/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/frame_owner.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/web_feature.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/loading_attribute.h"
namespace blink {
namespace {
// Returns true if absolute dimension is specified in the width and height
// attributes or in the inline style.
bool IsDimensionAbsoluteLarge(const HTMLImageElement& html_image) {
if (HTMLImageElement::GetAttributeLazyLoadDimensionType(
html_image.FastGetAttribute(html_names::kWidthAttr)) ==
HTMLImageElement::LazyLoadDimensionType::kAbsoluteNotSmall ||
HTMLImageElement::GetAttributeLazyLoadDimensionType(
html_image.FastGetAttribute(html_names::kHeightAttr)) ==
HTMLImageElement::LazyLoadDimensionType::kAbsoluteNotSmall) {
return true;
}
if (HTMLImageElement::GetInlineStyleDimensionsType(
html_image.InlineStyle()) ==
HTMLImageElement::LazyLoadDimensionType::kAbsoluteNotSmall) {
return true;
}
return false;
}
Document* GetRootDocumentOrNull(Element* element) {
if (LocalFrame* frame = element->GetDocument().GetFrame())
return frame->LocalFrameRoot().GetDocument();
return nullptr;
}
void StartMonitoringVisibility(HTMLImageElement* html_image) {
Document* document = GetRootDocumentOrNull(html_image);
if (document &&
RuntimeEnabledFeatures::LazyImageVisibleLoadTimeMetricsEnabled()) {
document->EnsureLazyLoadImageObserver().StartMonitoringVisibility(
document, html_image);
}
}
bool IsFullyLoadableFirstKImageAndDecrementCount(
HTMLImageElement* image_element) {
Document* document = GetRootDocumentOrNull(image_element);
if (!document)
return true;
return document->EnsureLazyLoadImageObserver()
.IsFullyLoadableFirstKImageAndDecrementCount();
}
} // namespace
// static
void LazyImageHelper::StartMonitoring(blink::Element* element) {
Document* document = GetRootDocumentOrNull(element);
if (!document)
return;
using DeferralMessage = LazyLoadImageObserver::DeferralMessage;
auto deferral_message = DeferralMessage::kNone;
if (auto* html_image = DynamicTo<HTMLImageElement>(element)) {
LoadingAttributeValue effective_loading_attr = GetLoadingAttributeValue(
html_image->FastGetAttribute(html_names::kLoadingAttr));
DCHECK_NE(effective_loading_attr, LoadingAttributeValue::kEager);
if (effective_loading_attr == LoadingAttributeValue::kAuto) {
deferral_message = DeferralMessage::kLoadEventsDeferred;
} else if (!IsDimensionAbsoluteLarge(*html_image)) {
DCHECK_EQ(effective_loading_attr, LoadingAttributeValue::kLazy);
deferral_message = DeferralMessage::kMissingDimensionForLazy;
}
}
document->EnsureLazyLoadImageObserver().StartMonitoringNearViewport(
document, element, deferral_message);
}
void LazyImageHelper::StopMonitoring(Element* element) {
if (Document* document = GetRootDocumentOrNull(element)) {
document->EnsureLazyLoadImageObserver().StopMonitoring(element);
}
}
// static
LazyImageHelper::Eligibility
LazyImageHelper::DetermineEligibilityAndTrackVisibilityMetrics(
LocalFrame& frame,
HTMLImageElement* html_image,
const KURL& url) {
if (!url.ProtocolIsInHTTPFamily())
return LazyImageHelper::Eligibility::kDisabled;
// Do not lazyload image elements when JavaScript is disabled, regardless of
// the `loading` attribute.
if (!frame.DomWindow()->CanExecuteScripts(kNotAboutToExecuteScript))
return LazyImageHelper::Eligibility::kDisabled;
const auto lazy_load_image_setting = frame.GetLazyLoadImageSetting();
LoadingAttributeValue loading_attr = GetLoadingAttributeValue(
html_image->FastGetAttribute(html_names::kLoadingAttr));
bool is_fully_loadable =
IsFullyLoadableFirstKImageAndDecrementCount(html_image);
if (loading_attr == LoadingAttributeValue::kLazy) {
StartMonitoringVisibility(html_image);
UseCounter::Count(frame.GetDocument(),
WebFeature::kLazyLoadImageLoadingAttributeLazy);
if (lazy_load_image_setting !=
LocalFrame::LazyLoadImageSetting::kDisabled) {
// Developer opt-in lazyload.
return LazyImageHelper::Eligibility::kEnabledFullyDeferred;
}
}
if (loading_attr == LoadingAttributeValue::kEager) {
UseCounter::Count(frame.GetDocument(),
WebFeature::kLazyLoadImageLoadingAttributeEager);
return LazyImageHelper::Eligibility::kDisabled;
}
// Do not lazyload image elements created from javascript.
if (!html_image->ElementCreatedByParser())
return LazyImageHelper::Eligibility::kDisabled;
if (frame.Owner() && !frame.Owner()->ShouldLazyLoadChildren())
return LazyImageHelper::Eligibility::kDisabled;
// Avoid automatically lazyloading if width and height attributes are small.
// This heuristic helps avoid double fetching tracking pixels.
if (HTMLImageElement::GetAttributeLazyLoadDimensionType(
html_image->FastGetAttribute(html_names::kWidthAttr)) ==
HTMLImageElement::LazyLoadDimensionType::kAbsoluteSmall &&
HTMLImageElement::GetAttributeLazyLoadDimensionType(
html_image->FastGetAttribute(html_names::kHeightAttr)) ==
HTMLImageElement::LazyLoadDimensionType::kAbsoluteSmall) {
return LazyImageHelper::Eligibility::kDisabled;
}
// Avoid automatically lazyloading if width or height is specified in inline
// style and is small enough. This heuristic helps avoid double fetching
// tracking pixels.
if (HTMLImageElement::GetInlineStyleDimensionsType(
html_image->InlineStyle()) ==
HTMLImageElement::LazyLoadDimensionType::kAbsoluteSmall) {
return LazyImageHelper::Eligibility::kDisabled;
}
StartMonitoringVisibility(html_image);
if (!is_fully_loadable &&
lazy_load_image_setting ==
LocalFrame::LazyLoadImageSetting::kEnabledAutomatic) {
// Automatic lazyload
return LazyImageHelper::Eligibility::kEnabledFullyDeferred;
}
return LazyImageHelper::Eligibility::kDisabled;
}
void LazyImageHelper::RecordMetricsOnLoadFinished(
HTMLImageElement* image_element) {
if (!RuntimeEnabledFeatures::LazyImageVisibleLoadTimeMetricsEnabled())
return;
if (Document* document = GetRootDocumentOrNull(image_element)) {
document->EnsureLazyLoadImageObserver().OnLoadFinished(image_element);
}
}
} // namespace blink