Commit f773967f authored by Yao Xiao's avatar Yao Xiao Committed by Commit Bot

Add a use counter for overlay popup

We used to measure overlay-popup-ad. This CL adds another use counter to
measure the overlay-popup in general, including both ads & non-ads.

This is achieved by adding extra flags to OverlayInterstitialAdDetector
to denote whether the current candidate is an ad, as well as whether we
have encountered ad and/or general interstitials.

Bug: 1048136
Change-Id: I6dc809ceed51aa9b9747af44d8db6ed38721bc2f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2250957
Commit-Queue: Yao Xiao <yaoxia@chromium.org>
Reviewed-by: default avatarBryan McQuade <bmcquade@chromium.org>
Reviewed-by: default avatarStephen Chenney <schenney@chromium.org>
Cr-Commit-Position: refs/heads/master@{#781231}
parent 715ceb2b
...@@ -160,6 +160,7 @@ UseCounterPageLoadMetricsObserver::GetAllowedUkmFeatures() { ...@@ -160,6 +160,7 @@ UseCounterPageLoadMetricsObserver::GetAllowedUkmFeatures() {
WebFeature::kThirdPartyCacheStorage, WebFeature::kThirdPartyCacheStorage,
WebFeature::kThirdPartyLocalStorage, WebFeature::kThirdPartyLocalStorage,
WebFeature::kThirdPartySessionStorage, WebFeature::kThirdPartySessionStorage,
WebFeature::kOverlayPopup,
WebFeature::kOverlayPopupAd, WebFeature::kOverlayPopupAd,
WebFeature::kTrustTokenXhr, WebFeature::kTrustTokenXhr,
WebFeature::kTrustTokenFetch, WebFeature::kTrustTokenFetch,
......
...@@ -2665,6 +2665,7 @@ enum WebFeature { ...@@ -2665,6 +2665,7 @@ enum WebFeature {
kWebBluetoothGetDevices = 3328, kWebBluetoothGetDevices = 3328,
kDialogWithNonZeroScrollOffset = 3329, kDialogWithNonZeroScrollOffset = 3329,
kDialogHeightLargerThanViewport = 3330, kDialogHeightLargerThanViewport = 3330,
kOverlayPopup = 3331,
// Add new features immediately above this line. Don't change assigned // Add new features immediately above this line. Don't change assigned
// numbers of any item, and don't reuse removed slots. // numbers of any item, and don't reuse removed slots.
......
...@@ -27,10 +27,7 @@ constexpr double kLargeAdSizeToViewportSizeThreshold = 0.1; ...@@ -27,10 +27,7 @@ constexpr double kLargeAdSizeToViewportSizeThreshold = 0.1;
// 2) <body> or <html> has style="overflow:hidden", and among its container // 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 // ancestors (including itself), the 2nd to the top (where the top should always
// be the <body>) has absolute position. // be the <body>) has absolute position.
bool IsOverlayAdCandidate(Element* element) { bool IsOverlayCandidate(Element* element) {
if (!element->IsAdRelated())
return false;
const ComputedStyle* style = nullptr; const ComputedStyle* style = nullptr;
LayoutView* layout_view = element->GetDocument().GetLayoutView(); LayoutView* layout_view = element->GetDocument().GetLayoutView();
LayoutObject* object = element->GetLayoutObject(); LayoutObject* object = element->GetLayoutObject();
...@@ -62,7 +59,7 @@ bool IsOverlayAdCandidate(Element* element) { ...@@ -62,7 +59,7 @@ bool IsOverlayAdCandidate(Element* element) {
void OverlayInterstitialAdDetector::MaybeFireDetection(LocalFrame* main_frame) { void OverlayInterstitialAdDetector::MaybeFireDetection(LocalFrame* main_frame) {
DCHECK(main_frame); DCHECK(main_frame);
DCHECK(main_frame->IsMainFrame()); DCHECK(main_frame->IsMainFrame());
if (done_detection_) if (popup_ad_detected_)
return; return;
DCHECK(main_frame->GetDocument()); DCHECK(main_frame->GetDocument());
...@@ -130,17 +127,22 @@ void OverlayInterstitialAdDetector::MaybeFireDetection(LocalFrame* main_frame) { ...@@ -130,17 +127,22 @@ void OverlayInterstitialAdDetector::MaybeFireDetection(LocalFrame* main_frame) {
bool is_new_element = (element_id != candidate_id_); bool is_new_element = (element_id != candidate_id_);
// The popup candidate has just been dismissed.
if (is_new_element && candidate_id_ != kInvalidDOMNodeId) { if (is_new_element && candidate_id_ != kInvalidDOMNodeId) {
// If the main frame scrolling offset hasn't changed since the candidate's // If the main frame scrolling offset hasn't changed since the candidate's
// appearance, we consider it to be a overlay interstitial; otherwise, we // appearance, we consider it to be a overlay interstitial; otherwise, we
// skip that candidate because it could be a parallax/scroller ad. // skip that candidate because it could be a parallax/scroller ad.
if (main_frame->GetMainFrameScrollOffset().Y() == if (main_frame->GetMainFrameScrollOffset().Y() ==
candidate_start_main_frame_scroll_offset_) { candidate_start_main_frame_scroll_offset_) {
OnPopupAdDetected(main_frame); OnPopupDetected(main_frame, candidate_is_ad_);
return;
} }
if (popup_ad_detected_)
return;
last_unqualified_element_id_ = candidate_id_; last_unqualified_element_id_ = candidate_id_;
candidate_id_ = kInvalidDOMNodeId; candidate_id_ = kInvalidDOMNodeId;
candidate_is_ad_ = false;
} }
if (!is_new_element) if (!is_new_element)
...@@ -159,16 +161,22 @@ void OverlayInterstitialAdDetector::MaybeFireDetection(LocalFrame* main_frame) { ...@@ -159,16 +161,22 @@ void OverlayInterstitialAdDetector::MaybeFireDetection(LocalFrame* main_frame) {
main_frame_size.Area() * kLargeAdSizeToViewportSizeThreshold); main_frame_size.Area() * kLargeAdSizeToViewportSizeThreshold);
bool has_gesture = LocalFrame::HasTransientUserActivation(main_frame); bool has_gesture = LocalFrame::HasTransientUserActivation(main_frame);
bool is_ad = element->IsAdRelated();
if (!has_gesture && is_large && IsOverlayAdCandidate(element)) { if (!has_gesture && is_large && (!popup_detected_ || is_ad) &&
IsOverlayCandidate(element)) {
// If main page is not scrollable, immediately determinine the overlay // If main page is not scrollable, immediately determinine the overlay
// to be a popup. There's is no need to check any state at the dismissal // to be a popup. There's is no need to check any state at the dismissal
// time. // time.
if (!main_frame->GetDocument()->GetLayoutView()->HasScrollableOverflowY()) { if (!main_frame->GetDocument()->GetLayoutView()->HasScrollableOverflowY()) {
OnPopupAdDetected(main_frame); OnPopupDetected(main_frame, is_ad);
return;
} }
if (popup_ad_detected_)
return;
candidate_id_ = element_id; candidate_id_ = element_id;
candidate_is_ad_ = is_ad;
candidate_start_main_frame_scroll_offset_ = candidate_start_main_frame_scroll_offset_ =
main_frame->GetMainFrameScrollOffset().Y(); main_frame->GetMainFrameScrollOffset().Y();
} else { } else {
...@@ -176,9 +184,18 @@ void OverlayInterstitialAdDetector::MaybeFireDetection(LocalFrame* main_frame) { ...@@ -176,9 +184,18 @@ void OverlayInterstitialAdDetector::MaybeFireDetection(LocalFrame* main_frame) {
} }
} }
void OverlayInterstitialAdDetector::OnPopupAdDetected(LocalFrame* main_frame) { void OverlayInterstitialAdDetector::OnPopupDetected(LocalFrame* main_frame,
UseCounter::Count(main_frame->GetDocument(), WebFeature::kOverlayPopupAd); bool is_ad) {
done_detection_ = true; if (!popup_detected_) {
UseCounter::Count(main_frame->GetDocument(), WebFeature::kOverlayPopup);
popup_detected_ = true;
}
if (is_ad) {
DCHECK(!popup_ad_detected_);
UseCounter::Count(main_frame->GetDocument(), WebFeature::kOverlayPopupAd);
popup_ad_detected_ = true;
}
} }
} // namespace blink } // namespace blink
...@@ -54,7 +54,7 @@ class CORE_EXPORT OverlayInterstitialAdDetector { ...@@ -54,7 +54,7 @@ class CORE_EXPORT OverlayInterstitialAdDetector {
void MaybeFireDetection(LocalFrame* main_frame); void MaybeFireDetection(LocalFrame* main_frame);
private: private:
void OnPopupAdDetected(LocalFrame* main_frame); void OnPopupDetected(LocalFrame* main_frame, bool is_ad);
bool started_detection_ = false; bool started_detection_ = false;
bool main_content_has_loaded_ = false; bool main_content_has_loaded_ = false;
...@@ -64,6 +64,7 @@ class CORE_EXPORT OverlayInterstitialAdDetector { ...@@ -64,6 +64,7 @@ class CORE_EXPORT OverlayInterstitialAdDetector {
IntSize last_detection_main_frame_size_; IntSize last_detection_main_frame_size_;
DOMNodeId candidate_id_; DOMNodeId candidate_id_;
bool candidate_is_ad_ = false;
// The following members are valid only when |candidate_| is not nullptr. // The following members are valid only when |candidate_| is not nullptr.
int candidate_start_main_frame_scroll_offset_ = 0; int candidate_start_main_frame_scroll_offset_ = 0;
...@@ -82,7 +83,8 @@ class CORE_EXPORT OverlayInterstitialAdDetector { ...@@ -82,7 +83,8 @@ class CORE_EXPORT OverlayInterstitialAdDetector {
// can skip it on its next occurrence without computing the style again. // can skip it on its next occurrence without computing the style again.
DOMNodeId last_unqualified_element_id_; DOMNodeId last_unqualified_element_id_;
bool done_detection_ = false; bool popup_detected_ = false;
bool popup_ad_detected_ = false;
DISALLOW_COPY_AND_ASSIGN(OverlayInterstitialAdDetector); DISALLOW_COPY_AND_ASSIGN(OverlayInterstitialAdDetector);
}; };
......
...@@ -539,3 +539,6 @@ crbug.com/1043669 [ Mac ] inspector-protocol/emulation/set-vision-deficiency.js ...@@ -539,3 +539,6 @@ crbug.com/1043669 [ Mac ] inspector-protocol/emulation/set-vision-deficiency.js
crbug.com/1092121 fast/css/large-list-of-rules-crash.html [ Slow ] crbug.com/1092121 fast/css/large-list-of-rules-crash.html [ Slow ]
crbug.com/1042205 virtual/off-main-thread-css-paint/external/wpt/css/css-paint-api/paint2d-filter.https.html [ Slow ] crbug.com/1042205 virtual/off-main-thread-css-paint/external/wpt/css/css-paint-api/paint2d-filter.https.html [ Slow ]
# Slow is the intended behavior
crbug.com/1048136 http/tests/subresource_filter/overlay-popup-non-ad-followed-by-ad.html [ Slow ]
<!DOCTYPE html>
<html>
<head>
<style>
div {
width: 100vw;
height: 100vh;
}
iframe {
position: fixed;
margin-left: 25vw;
width: 50vw;
height: 100vh;
border: 0px;
}
form {
position: fixed;
margin-left: 25vw;
width: 50vw;
height: 100vh;
border: 0px;
}
p {
position: fixed;
}
div.bottom {
position: absolute;
top: 10000px;
left: 0px;
width: 1px;
height: 1px;
}
</style>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
</head>
<body marginwidth="0" marginheight="0">
<!-- To trigger the first contentful paint at the very start -->
<p>some content</p>
<!-- To contain the overlay popup -->
<div></div>
<!-- To be positioned further down in the main page to make the page scrollable -->
<div class="bottom"></div>
<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 */);
}
promise_test(() => {
return new Promise((resolve, reject) => {
let ad_script = document.createElement("script");
ad_script.async = false;
ad_script.src = "resources/overlay-interstitial-ad-testharness.js";
ad_script.onload = async() => {
// After 1500ms, force a layout update so that the interstitial detector
// is aware of the first meaningful paint, and future overlay candidates
// will be considered for pop-ups rather than for prestitials.
await timeout(1500);
await forceLayoutUpdate();
// Create a overlay pop-up <form> element.
let form = document.createElement('form');
document.getElementsByTagName('div')[0].appendChild(form);
// After 1500ms, force a layout update so that the interstitial detector
// is aware of the overlay candidate.
await timeout(1500);
await forceLayoutUpdate();
// Hide the pop-up.
document.getElementsByTagName('form')[0].style.display = 'none';
// After 1500ms, force a layout update so that the interstitial detector
// is aware of the overlay candidate's dismissal.
await timeout(1500);
await forceLayoutUpdate();
// Expect use counter kOverlayPopup.
if (!internals.isUseCounted(document, kOverlayPopup)) {
reject();
}
// Expect use counter kOverlayPopupAd is NOT recorded.
if (internals.isUseCounted(document, kOverlayPopupAd)) {
reject();
}
// Create the overlay pop-up ad.
appendAdFrameTo(document.getElementsByTagName('div')[0]);
// After 1500ms, force a layout update so that the interstitial detector
// is aware of the overlay ad candidate.
await timeout(1500);
await forceLayoutUpdate();
// Hide the pop-up ad.
document.getElementsByTagName('iframe')[0].style.display = 'none';
// After 1500ms, force a layout update so that the interstitial detector
// is aware of the overlay ad candidate's dismissal.
await timeout(1500);
await forceLayoutUpdate();
// Expect use counter kOverlayPopupAd.
if (!internals.isUseCounted(document, kOverlayPopupAd)) {
reject();
}
resolve();
};
document.body.appendChild(ad_script);
});
}, "Test UseCounter for overlay-popup non-ad element followed by an ad element.");
</script>
</body>
</html>
// |kOverlayPopupAd| from web_feature.mojom. // |kOverlayPopupAd| from web_feature.mojom.
const kOverlayPopupAd = 3253; const kOverlayPopupAd = 3253;
// |kOverlayPopup| from web_feature.mojom.
const kOverlayPopup = 3331;
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function forceLayoutUpdate() {
return new Promise((resolve) => requestAnimationFrame(() => { setTimeout(() => { resolve(); }) }));
}
// TODO(yaoxia): For those tests that still depend on this function, we'd want
// to convert them use Promise and async/await for better readability.
function verifyOverlayPopupUseCounterAfter1500ms( function verifyOverlayPopupUseCounterAfter1500ms(
test, expect_use_counter, post_verify_callback) { test, expect_use_counter, post_verify_callback) {
// Force a lifecycle update after 1500ms, so that the final stable layout // Force a lifecycle update after 1500ms, so that the final stable layout
......
...@@ -28066,6 +28066,7 @@ Called by update_use_counter_feature_enum.py.--> ...@@ -28066,6 +28066,7 @@ Called by update_use_counter_feature_enum.py.-->
<int value="3328" label="WebBluetoothGetDevices"/> <int value="3328" label="WebBluetoothGetDevices"/>
<int value="3329" label="DialogWithNonZeroScrollOffset"/> <int value="3329" label="DialogWithNonZeroScrollOffset"/>
<int value="3330" label="DialogHeightLargerThanViewport"/> <int value="3330" label="DialogHeightLargerThanViewport"/>
<int value="3331" label="OverlayPopup"/>
</enum> </enum>
<enum name="FeaturePolicyAllowlistType"> <enum name="FeaturePolicyAllowlistType">
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