Commit c3bf6a24 authored by Stefan Zager's avatar Stefan Zager Committed by Commit Bot

[IntersectionObserver] Throttle v2 observations to 100ms intervals

Since determining occlusion is much more expensive than computing
clipping, throttle the frequency of IntersectionObserver notifications
to no more than one every 100ms.

BUG=827639
R=chrishtr@chromium.org

Change-Id: I6675482041418c628dbddfd648319d18d871d38b
Reviewed-on: https://chromium-review.googlesource.com/1175210Reviewed-by: default avatarChris Harrelson <chrishtr@chromium.org>
Reviewed-by: default avatarStefan Zager <szager@chromium.org>
Commit-Queue: Stefan Zager <szager@chromium.org>
Cr-Commit-Position: refs/heads/master@{#585725}
parent 9eb75108
......@@ -3,10 +3,18 @@
<script>
var results = [];
// This wraps the usual definition of waitForNotification in a 100ms setTimeout.
// The extra delay is necessary to ensure that notifications have been generated
// when the observer is tracking visibility; see:
// http://szager-chromium.github.io/IntersectionObserver/#dom-intersectionobserver-trackvisibility
function waitForNotification(f) {
requestAnimationFrame(function () {
setTimeout(function () { setTimeout(f); });
});
setTimeout(() => {
requestAnimationFrame(function () {
setTimeout(function () {
setTimeout(f)
})
})
}, 100)
}
onload = () => {
......
......@@ -41,6 +41,20 @@ function runTestCycle(f, description) {
}, description);
}
// This wraps waitForNotification in a 100ms setTimeout. The extra delay is
// necessary to ensure that notifications have been generated when the observer
// is tracking visibility; see:
// http://szager-chromium.github.io/IntersectionObserver/#dom-intersectionobserver-trackvisibility
function waitForNotificationV2(f) {
setTimeout(() => { waitForNotification(f) }, 100);
}
function runTestCycleV2(f, description) {
async_test(function(t) {
waitForNotificationV2(t.step_func_done(f));
}, description);
}
// Root bounds for a root with an overflow clip as defined by:
// http://wicg.github.io/IntersectionObserver/#intersectionobserver-root-intersection-rectangle
function contentBounds(root) {
......
......@@ -45,7 +45,7 @@ if (window.internals && internals.runtimeFlags) {
internals.runtimeFlags.intersectionObserverV2Enabled = true;
}
runTestCycle(function() {
runTestCycleV2(function() {
assert_equals(window.innerWidth, 800, "Window must be 800 pixels wide.");
assert_equals(window.innerHeight, 600, "Window must be 600 pixels high.");
......@@ -59,13 +59,13 @@ runTestCycle(function() {
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(step0, "First rAF.");
runTestCycleV2(step0, "First rAF.");
}, "IntersectionObserverV2 in a single document using the implicit root, with an animated occluding element.");
function step0() {
occluder.style.animation = "rotate .1s linear";
setTimeout(() => {
runTestCycle(step1, "occluder.style.animation = 'rotate .1s linear'");
runTestCycleV2(step1, "occluder.style.animation = 'rotate .1s linear'");
}, 50);
checkLastEntry(entries, 0, [0, 100, 0, 100, 0, 100, 0, 100, 0, 800, 0, 600, true, true]);
}
......
......@@ -36,7 +36,7 @@ if (window.internals && internals.runtimeFlags) {
internals.runtimeFlags.intersectionObserverV2Enabled = true;
}
runTestCycle(function() {
runTestCycleV2(function() {
target = document.getElementById("target");
effects = document.getElementById("effects");
assert_true(!!target, "target exists");
......@@ -47,24 +47,24 @@ runTestCycle(function() {
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(step0, "First rAF.");
runTestCycleV2(step0, "First rAF.");
}, "IntersectionObserverV2 in a single document using the implicit root, with a non-zero opacity ancestor.");
function step0() {
effects.style.opacity = "0.99";
runTestCycle(step1, "effects.style.opacity = 0.99");
runTestCycleV2(step1, "effects.style.opacity = 0.99");
checkLastEntry(entries, 0, [0, 100, 0, 100, 0, 100, 0, 100, 0, 800, 0, 600, true, true]);
}
function step1() {
effects.style.opacity = "1";
runTestCycle(step2, "effects.style.opacity = 1");
runTestCycleV2(step2, "effects.style.opacity = 1");
checkLastEntry(entries, 1, [0, 100, 0, 100, 0, 100, 0, 100, 0, 800, 0, 600, true, false]);
}
function step2() {
effects.style.filter = "grayscale(50%)";
runTestCycle(step3, "effects.style.filter = grayscale(50%)");
runTestCycleV2(step3, "effects.style.filter = grayscale(50%)");
checkLastEntry(entries, 2, [0, 100, 0, 100, 0, 100, 0, 100, 0, 800, 0, 600, true, true]);
}
......
......@@ -36,7 +36,7 @@ if (window.internals && internals.runtimeFlags) {
internals.runtimeFlags.intersectionObserverV2Enabled = true;
}
runTestCycle(function() {
runTestCycleV2(function() {
target = document.getElementById("target");
occluder = document.getElementById("occluder");
assert_true(!!target, "target exists");
......@@ -47,19 +47,19 @@ runTestCycle(function() {
observer.observe(target);
entries = entries.concat(observer.takeRecords());
assert_equals(entries.length, 0, "No initial notifications.");
runTestCycle(step0, "First rAF.");
runTestCycleV2(step0, "First rAF.");
}, "IntersectionObserverV2 in a single document using the implicit root, with an occluding element.");
function step0() {
occluder.style.marginTop = "-10px";
runTestCycle(step1, "occluder.style.marginTop = '-10px'");
runTestCycleV2(step1, "occluder.style.marginTop = '-10px'");
checkLastEntry(entries, 0, [0, 100, 0, 100, 0, 100, 0, 100, 0, 800, 0, 600, true, true]);
}
function step1() {
// Occluding elements with opacity=0 should not affect target visibility.
occluder.style.opacity = "0";
runTestCycle(step2, "occluder.style.opacity = 0");
runTestCycleV2(step2, "occluder.style.opacity = 0");
checkLastEntry(entries, 1, [0, 100, 0, 100, 0, 100, 0, 100, 0, 800, 0, 600, true, false]);
}
......
......@@ -135,6 +135,15 @@ void ParseThresholds(const DoubleOrDoubleSequence& threshold_parameter,
} // anonymous namespace
// Minimum time, in milliseconds, between observations. See:
// http://szager-chromium.github.io/IntersectionObserver/#dom-intersectionobserver-trackvisibility
const DOMHighResTimeStamp IntersectionObserver::s_v2_throttle_delay_ = 100;
static bool v2_throttle_delay_enabled = true;
void IntersectionObserver::SetV2ThrottleDelayEnabledForTesting(bool enabled) {
v2_throttle_delay_enabled = enabled;
}
IntersectionObserver* IntersectionObserver::Create(
const IntersectionObserverInit& observer_init,
IntersectionObserverDelegate& delegate,
......@@ -196,6 +205,7 @@ IntersectionObserver::IntersectionObserver(
right_margin_(kFixed),
bottom_margin_(kFixed),
left_margin_(kFixed),
last_run_time_(-s_v2_throttle_delay_),
root_is_implicit_(root ? 0 : 1),
track_visibility_(track_visibility ? 1 : 0) {
switch (root_margin.size()) {
......@@ -311,8 +321,13 @@ void IntersectionObserver::ComputeIntersectionObservations() {
return;
DOMHighResTimeStamp timestamp =
DOMWindowPerformance::performance(*delegate_dom_window)->now();
if (track_visibility_ && v2_throttle_delay_enabled &&
timestamp - last_run_time_ < s_v2_throttle_delay_) {
return;
}
last_run_time_ = timestamp;
for (auto& observation : observations_)
observation->ComputeIntersectionObservations(timestamp);
observation->ComputeIntersectionObservations(last_run_time_);
}
void IntersectionObserver::disconnect(ExceptionState& exception_state) {
......
......@@ -97,6 +97,10 @@ class CORE_EXPORT IntersectionObserver final
void Trace(blink::Visitor*) override;
// Enable/disable throttling of visibility checking, so we don't have to add
// 100ms sleep() calls to tests.
static void SetV2ThrottleDelayEnabledForTesting(bool);
private:
explicit IntersectionObserver(IntersectionObserverDelegate&,
Element*,
......@@ -109,6 +113,10 @@ class CORE_EXPORT IntersectionObserver final
// deleted; true otherwise.
bool RootIsValid() const;
// If trackVisibility is true, don't compute observations more frequently
// than this many milliseconds.
static const DOMHighResTimeStamp s_v2_throttle_delay_;
const TraceWrapperMember<IntersectionObserverDelegate> delegate_;
WeakMember<Element> root_;
HeapLinkedHashSet<WeakMember<IntersectionObservation>> observations_;
......@@ -118,6 +126,7 @@ class CORE_EXPORT IntersectionObserver final
Length right_margin_;
Length bottom_margin_;
Length left_margin_;
DOMHighResTimeStamp last_run_time_;
unsigned root_is_implicit_ : 1;
unsigned track_visibility_ : 1;
};
......
......@@ -66,7 +66,13 @@ class IntersectionObserverV2Test : public IntersectionObserverTest,
public ScopedIntersectionObserverV2ForTest {
public:
IntersectionObserverV2Test()
: IntersectionObserverTest(), ScopedIntersectionObserverV2ForTest(true) {}
: IntersectionObserverTest(), ScopedIntersectionObserverV2ForTest(true) {
IntersectionObserver::SetV2ThrottleDelayEnabledForTesting(false);
}
~IntersectionObserverV2Test() override {
IntersectionObserver::SetV2ThrottleDelayEnabledForTesting(true);
}
};
TEST_F(IntersectionObserverTest, ObserveSchedulesFrame) {
......
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