Commit 0530ba2f authored by Yao Xiao's avatar Yao Xiao Committed by Commit Bot

Reland overlay-interstitial-ad detection, switching to HitTestNoLifecycleUpdate

What: This relands http://crrev/c/1993140 that was reverted in
http://crrev/c/2035374. The diff:
in OverlayInterstitialAdDetector::MaybeFireDetection, switch to
main_frame->ContentLayoutObject()->HitTestNoLifecycleUpdate for the hit
test.

Why: We don't need LocalFrameView::UpdateAllLifecyclePhasesExceptPaint
as we don't need to recurse into child document. Forcing the update
would change the document lifecycle state, that would break the test
expectations of FrameThrottlingTest.ThrottledLifecycleUpdate and would
introduce flakes for AccessibilityActionBrowserTest.ImgElementGetImage.


Bug: 1047921
Change-Id: Ic99420e125788d446bb11202848634ddc6e07841
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2034054
Commit-Queue: Yao Xiao <yaoxia@chromium.org>
Reviewed-by: default avatarStefan Zager <szager@chromium.org>
Reviewed-by: default avatarBryan McQuade <bmcquade@chromium.org>
Cr-Commit-Position: refs/heads/master@{#738382}
parent 08451a4e
...@@ -150,6 +150,7 @@ UseCounterPageLoadMetricsObserver::GetAllowedUkmFeatures() { ...@@ -150,6 +150,7 @@ UseCounterPageLoadMetricsObserver::GetAllowedUkmFeatures() {
WebFeature::kV8MediaSession_Metadata_AttributeSetter, WebFeature::kV8MediaSession_Metadata_AttributeSetter,
WebFeature::kV8MediaSession_SetActionHandler_Method, WebFeature::kV8MediaSession_SetActionHandler_Method,
WebFeature::kLargeStickyAd, WebFeature::kLargeStickyAd,
WebFeature::kOverlayInterstitialAd,
})); }));
return *opt_in_features; return *opt_in_features;
} }
...@@ -128,6 +128,8 @@ blink_core_sources("frame") { ...@@ -128,6 +128,8 @@ blink_core_sources("frame") {
"navigator_user_agent.h", "navigator_user_agent.h",
"opened_frame_tracker.cc", "opened_frame_tracker.cc",
"opened_frame_tracker.h", "opened_frame_tracker.h",
"overlay_interstitial_ad_detector.cc",
"overlay_interstitial_ad_detector.h",
"page_scale_constraints.cc", "page_scale_constraints.cc",
"page_scale_constraints.h", "page_scale_constraints.h",
"page_scale_constraints_set.cc", "page_scale_constraints_set.cc",
......
...@@ -1028,6 +1028,10 @@ void LocalFrameView::RunIntersectionObserverSteps() { ...@@ -1028,6 +1028,10 @@ void LocalFrameView::RunIntersectionObserverSteps() {
!frame_->GetDocument()->IsActive()) { !frame_->GetDocument()->IsActive()) {
return; return;
} }
if (frame_->IsMainFrame())
EnsureOverlayInterstitialAdDetector().MaybeFireDetection(frame_.Get());
TRACE_EVENT0("blink,benchmark", TRACE_EVENT0("blink,benchmark",
"LocalFrameView::UpdateViewportIntersectionsForSubtree"); "LocalFrameView::UpdateViewportIntersectionsForSubtree");
SCOPED_UMA_AND_UKM_TIMER(EnsureUkmAggregator(), SCOPED_UMA_AND_UKM_TIMER(EnsureUkmAggregator(),
...@@ -4459,4 +4463,13 @@ void LocalFrameView::OnPurgeMemory() { ...@@ -4459,4 +4463,13 @@ void LocalFrameView::OnPurgeMemory() {
received_foreground_compositor_memory_pressure_purge_signal_ = true; received_foreground_compositor_memory_pressure_purge_signal_ = true;
} }
OverlayInterstitialAdDetector&
LocalFrameView::EnsureOverlayInterstitialAdDetector() {
if (!overlay_interstitial_ad_detector_) {
overlay_interstitial_ad_detector_ =
std::make_unique<OverlayInterstitialAdDetector>();
}
return *overlay_interstitial_ad_detector_.get();
}
} // namespace blink } // namespace blink
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
#include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/frame/frame_view.h" #include "third_party/blink/renderer/core/frame/frame_view.h"
#include "third_party/blink/renderer/core/frame/layout_subtree_root_list.h" #include "third_party/blink/renderer/core/frame/layout_subtree_root_list.h"
#include "third_party/blink/renderer/core/frame/overlay_interstitial_ad_detector.h"
#include "third_party/blink/renderer/core/layout/depth_ordered_layout_object_list.h" #include "third_party/blink/renderer/core/layout/depth_ordered_layout_object_list.h"
#include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h" #include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h"
#include "third_party/blink/renderer/core/paint/layout_object_counter.h" #include "third_party/blink/renderer/core/paint/layout_object_counter.h"
...@@ -836,6 +837,10 @@ class CORE_EXPORT LocalFrameView final ...@@ -836,6 +837,10 @@ class CORE_EXPORT LocalFrameView final
// MemoryPressureListener // MemoryPressureListener
void OnPurgeMemory() override; void OnPurgeMemory() override;
// Return the interstitial-ad detector for this frame, creating it if
// necessary.
OverlayInterstitialAdDetector& EnsureOverlayInterstitialAdDetector();
LayoutSize size_; LayoutSize size_;
typedef HashSet<scoped_refptr<LayoutEmbeddedObject>> EmbeddedObjectSet; typedef HashSet<scoped_refptr<LayoutEmbeddedObject>> EmbeddedObjectSet;
...@@ -995,6 +1000,9 @@ class CORE_EXPORT LocalFrameView final ...@@ -995,6 +1000,9 @@ class CORE_EXPORT LocalFrameView final
// it can clear the painted output. // it can clear the painted output.
bool need_paint_phase_after_throttling_ = false; bool need_paint_phase_after_throttling_ = false;
std::unique_ptr<OverlayInterstitialAdDetector>
overlay_interstitial_ad_detector_;
#if DCHECK_IS_ON() #if DCHECK_IS_ON()
bool is_updating_descendant_dependent_flags_; bool is_updating_descendant_dependent_flags_;
#endif #endif
......
// Copyright 2020 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/frame/overlay_interstitial_ad_detector.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
#include "third_party/blink/renderer/core/html/html_image_element.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/layout_object_inlines.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
namespace blink {
namespace {
constexpr base::TimeDelta kFireInterval = base::TimeDelta::FromSeconds(1);
bool IsIframeAd(Element* element) {
HTMLFrameOwnerElement* frame_owner_element =
DynamicTo<HTMLFrameOwnerElement>(element);
if (!frame_owner_element)
return false;
Frame* frame = frame_owner_element->ContentFrame();
return frame && frame->IsAdSubframe();
}
bool IsImageAd(Element* element) {
HTMLImageElement* image_element = DynamicTo<HTMLImageElement>(element);
if (!image_element)
return false;
// TODO(yaoxia): image will be tagged soon.
return false;
}
// An overlay interstitial element shouldn't move with scrolling and should be
// able to overlap with other contents. So, either:
// 1) one of its container ancestors (including itself) has fixed position.
// 2) <body> or <html> has style="overflow:hidden", and among its container
// ancestors (including itself), the 2nd to the top (where the top should always
// be the <body>) has absolute position.
bool IsImmobileAndCanOverlapWithOtherContent(Element* element) {
const ComputedStyle* style = nullptr;
LayoutView* layout_view = element->GetDocument().GetLayoutView();
LayoutObject* object = element->GetLayoutObject();
DCHECK_NE(object, layout_view);
for (; object != layout_view; object = object->Container()) {
DCHECK(object);
style = object->Style();
}
DCHECK(style);
// 'style' is now the ComputedStyle for the object whose position depends
// on the document.
if (style->HasViewportConstrainedPosition() ||
style->HasStickyConstrainedPosition()) {
return true;
}
if (style->GetPosition() == EPosition::kAbsolute)
return !object->StyleRef().ScrollsOverflow();
return false;
}
bool IsInterstitialAd(Element* element) {
return (IsIframeAd(element) || IsImageAd(element)) &&
IsImmobileAndCanOverlapWithOtherContent(element);
}
} // namespace
void OverlayInterstitialAdDetector::MaybeFireDetection(LocalFrame* main_frame) {
DCHECK(main_frame);
DCHECK(main_frame->IsMainFrame());
if (done_detection_)
return;
DCHECK(main_frame->GetDocument());
DCHECK(main_frame->ContentLayoutObject());
base::Time current_time = base::Time::Now();
if (!last_detection_time_.has_value() ||
current_time - last_detection_time_.value() >= kFireInterval) {
IntSize main_frame_size =
main_frame->View()->GetScrollableArea()->VisibleContentRect().Size();
HitTestLocation location(DoublePoint(main_frame_size.Width() / 2.0,
main_frame_size.Height() / 2.0));
HitTestResult result;
main_frame->ContentLayoutObject()->HitTestNoLifecycleUpdate(location,
result);
Element* element = result.InnerElement();
if (element && IsInterstitialAd(element)) {
UseCounter::Count(main_frame->GetDocument(),
WebFeature::kOverlayInterstitialAd);
done_detection_ = true;
}
last_detection_time_ = current_time;
}
}
} // namespace blink
// Copyright 2020 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_FRAME_OVERLAY_INTERSTITIAL_AD_DETECTOR_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_OVERLAY_INTERSTITIAL_AD_DETECTOR_H_
#include "base/optional.h"
#include "base/time/time.h"
#include "third_party/blink/renderer/core/core_export.h"
namespace blink {
class LocalFrame;
class CORE_EXPORT OverlayInterstitialAdDetector {
public:
OverlayInterstitialAdDetector() = default;
~OverlayInterstitialAdDetector() = default;
void MaybeFireDetection(LocalFrame* main_frame);
private:
base::Optional<base::Time> last_detection_time_;
bool done_detection_ = false;
DISALLOW_COPY_AND_ASSIGN(OverlayInterstitialAdDetector);
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_OVERLAY_INTERSTITIAL_AD_DETECTOR_H_
<!DOCTYPE html>
<html>
<head>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
</head>
<body>
<script>
if (window.testRunner) {
// Inject a subresource filter to mark 'overlay-interstitial-ad-testharness.js' as a would be disallowed resource.
testRunner.setDisallowedSubresourcePathSuffixes(["overlay-interstitial-ad-testharness.js"], false /* block_subresources */);
}
async_test(t => {
let ad_script = document.createElement("script");
ad_script.async = false;
ad_script.src = "resources/overlay-interstitial-ad-testharness.js";
ad_script.onload = t.step_func(() => {
// Add an element with height:2000px at the end of the body, to make sure the document overflows.
let div = document.createElement("div");
div.style.height = '2000px';
document.body.appendChild(div);
// Create a frame in an absolute positioned div.
createInterstitialAdFrameAbsoluteOuterDiv();
// After 1500ms verify no interstitial was detected. Then make the <body>
// overflow:hidden, and after 1500ms verify an interstitial was detected.
verifyInterstitialUseCounterAfter1500ms(t, false,
(test) => {
document.body.style.overflow = "hidden";
verifyInterstitialUseCounterAfter1500ms(test, true);
});
});
document.body.appendChild(ad_script);
}, "Test UseCounter for overlay-interstitial-ad when the frame is in a absolute positioned div and the <body> has overflow:hidden.");
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
</head>
<body>
<script>
if (window.testRunner) {
// Inject a subresource filter to mark 'overlay-interstitial-ad-testharness.js' as a would be disallowed resource.
testRunner.setDisallowedSubresourcePathSuffixes(["overlay-interstitial-ad-testharness.js"], false /* block_subresources */);
}
async_test(t => {
let ad_script = document.createElement("script");
ad_script.async = false;
ad_script.src = "resources/overlay-interstitial-ad-testharness.js";
ad_script.onload = t.step_func(() => {
createInterstitialAdFrameFixedFrame();
verifyInterstitialUseCounterAfter1500ms(t, true);
});
document.body.appendChild(ad_script);
}, "Test UseCounter for overlay-interstitial-ad when the frame itself has fixed position.");
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
</head>
<body>
<script>
if (window.testRunner) {
// Inject a subresource filter to mark 'overlay-interstitial-ad-testharness.js' as a would be disallowed resource.
testRunner.setDisallowedSubresourcePathSuffixes(["overlay-interstitial-ad-testharness.js"], false /* block_subresources */);
}
async_test(t => {
let ad_script = document.createElement("script");
ad_script.async = false;
ad_script.src = "resources/overlay-interstitial-ad-testharness.js";
ad_script.onload = t.step_func(() => {
createInterstitialAdFrameFixedOuterDiv();
verifyInterstitialUseCounterAfter1500ms(t, true);
});
document.body.appendChild(ad_script);
}, "Test UseCounter for overlay-interstitial-ad when the outer div of a frame has fixed position.");
</script>
</body>
</html>
// |kOverlayInterstitialAd| from web_feature.mojom.
const kOverlayInterstitialAd = 3156;
function verifyInterstitialUseCounterAfter1500ms(
test, expect_use_counter, post_verify_callback) {
// Force a lifecycle update after 1500ms, so that the final stable layout
// can be captured.
setTimeout(
test.step_func(() => {
requestAnimationFrame(test.step_func(() => {
// This runs just prior to the lifecycle update.
setTimeout(test.step_func(() => {
// This code runs immediately after the lifecycle update.
if (self.internals)
assert_equals(
internals.isUseCounted(document, kOverlayInterstitialAd),
expect_use_counter);
if (post_verify_callback !== undefined) {
post_verify_callback(test);
} else {
test.done();
}
}));
}));
}),
1500);
}
// Set up div(div(iframe)) where the outer div has fixed position.
function createInterstitialAdFrameFixedOuterDiv() {
let div1 = document.createElement('div');
div1.style.position = 'fixed';
div1.style.top = '0px';
div1.style.left = '0px';
div1.style.width = '100%';
div1.style.height = '100%';
let div2 = document.createElement('div');
div2.style.width = '100%';
div2.style.height = '100%';
let ad_frame = document.createElement('iframe');
ad_frame.style.width = '50%';
ad_frame.style.height = '100%';
div2.appendChild(ad_frame);
div1.appendChild(div2);
document.body.appendChild(div1);
return ad_frame;
}
// Set up div(div(iframe)) where the frame has fixed position.
function createInterstitialAdFrameFixedFrame() {
let div1 = document.createElement('div');
div1.style.width = '100%';
div1.style.height = '100%';
let div2 = document.createElement('div');
div2.style.width = '100%';
div2.style.height = '100%';
let ad_frame = document.createElement('iframe');
ad_frame.style.position = 'fixed';
ad_frame.style.top = '0px';
ad_frame.style.left = '0px';
ad_frame.style.width = '50%';
ad_frame.style.height = '100%';
div2.appendChild(ad_frame);
div1.appendChild(div2);
document.body.appendChild(div1);
return ad_frame;
}
// Set up div(div(iframe)) where the outer div has absolute position.
function createInterstitialAdFrameAbsoluteOuterDiv() {
let div1 = document.createElement('div');
div1.style.position = 'absolute';
div1.style.top = '0px';
div1.style.left = '0px';
div1.style.width = '100%';
div1.style.height = '100%';
let div2 = document.createElement('div');
div2.style.width = '100%';
div2.style.height = '100%';
let ad_frame = document.createElement('iframe');
ad_frame.style.width = '50%';
ad_frame.style.height = '100%';
div2.appendChild(ad_frame);
div1.appendChild(div2);
document.body.appendChild(div1);
return ad_frame;
}
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