// TODO(crbug.com/888443): It would be better to listen to the scrollend event // instead of polling the scroll position. function observeScrolling(elements, callback) { if (!Array.isArray(elements)) elements = [elements]; var lastChangedFrame = 0; var lastLeft = new Map(); var lastTop = new Map(); elements.forEach((element) => { lastLeft.set(element, element.scrollLeft); lastTop.set(element, element.scrollTop); }); function tick(frames) { // We requestAnimationFrame either for 5000 frames or until 20 frames with // no change have been observed. (In Chromium, frames may run as frequently // as once per millisecond when threaded compositing is disabled. The limit // of 5000 frames is chosen to be high enough to reasonably ensure any // scroll animation will run to completion.) if (frames >= 5000 || frames - lastChangedFrame > 20) { callback(true); } else { var scrollHappened = elements.some((element) => { return element.scrollLeft != lastLeft.get(element) || element.scrollTop != lastTop.get(element); }); if (scrollHappened) { lastChangedFrame = frames; elements.forEach((element) => { lastLeft.set(element, element.scrollLeft); lastTop.set(element, element.scrollTop); }); callback(false); } requestAnimationFrame(tick.bind(null, frames + 1)); } } tick(0); } function waitForScrollEnd(elements) { return new Promise((resolve) => { observeScrolling(elements, (done) => { if (done) resolve(); }); }); } function resetScroll(scrollingElement) { // Try various methods to ensure the element position is reset immediately. scrollingElement.scrollLeft = 0; scrollingElement.scrollTop = 0; scrollingElement.scroll({left: 0, top: 0, behavior: "instant"}); } function resetScrollForWindow(scrollingWindow) { // Try various methods to ensure the element position is reset immediately. scrollingWindow.document.scrollingElement.scrollLeft = 0; scrollingWindow.document.scrollingElement.scrollTop = 0; scrollingWindow.scroll({left: 0, top: 0, behavior: "instant"}); } function setScrollBehavior(styledElement, className) { styledElement.classList.remove("autoBehavior", "smoothBehavior"); styledElement.classList.add(className); } function scrollNode(scrollingElement, scrollFunction, behavior, elementToRevealLeft, elementToRevealTop) { var args = {}; if (behavior) args.behavior = behavior; switch (scrollFunction) { case "scrollIntoView": args.inline = "start"; args.block = "start"; elementToReveal.scrollIntoView(args); break; default: args.left = elementToRevealLeft; args.top = elementToRevealTop; scrollingElement[scrollFunction](args); break; } } function scrollWindow(scrollingWindow, scrollFunction, behavior, elementToRevealLeft, elementToRevealTop) { var args = {}; if (behavior) args.behavior = behavior; args.left = elementToRevealLeft; args.top = elementToRevealTop; scrollingWindow[scrollFunction](args); }