From 0d5d6d07c8f3cdc1dc94f57223c50ac4c5ce638c Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sat, 7 Oct 2023 11:10:36 -0700 Subject: [PATCH] rustdoc: avoid whole page layout on each frame This makes two changes, based on experimenting with different browsers: - It debounces resizing the body text. This improves behavior on huge pages like struct.Vec.html, because it doesn't have to do layout. - It does the sidebar width updates directly on the sidebar instead of doing it on the `` element. Doing it on `` causes it to recalculate CSS for the entire document, also causing layout jank. --- src/librustdoc/html/static/css/rustdoc.css | 18 +++++-- src/librustdoc/html/static/js/main.js | 57 ++++++++++++++++++++-- src/librustdoc/html/static_files.rs | 1 - 3 files changed, 67 insertions(+), 9 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index d978d5bcb7554..c66425dfa9b2e 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -389,12 +389,14 @@ img { .sidebar { font-size: 0.875rem; flex: 0 0 var(--desktop-sidebar-width); + width: var(--desktop-sidebar-width); overflow-y: scroll; overscroll-behavior: contain; position: sticky; height: 100vh; top: 0; left: 0; + z-index: 100; } .rustdoc.src .sidebar { @@ -403,7 +405,6 @@ img { overflow-x: hidden; /* The sidebar is by default hidden */ overflow-y: hidden; - z-index: 1; } .hide-sidebar .sidebar, @@ -415,8 +416,8 @@ img { touch-action: none; width: 9px; cursor: col-resize; - z-index: 10; - position: absolute; + z-index: 200; + position: fixed; height: 100%; /* make sure there's a 1px gap between the scrollbar and resize handle */ left: calc(var(--desktop-sidebar-width) + 1px); @@ -445,6 +446,14 @@ img { cursor: col-resize !important; } +.sidebar-resizing .sidebar { + position: fixed; + z-index: 100; +} +.sidebar-resizing > body { + padding-left: var(--resizing-sidebar-width); +} + .sidebar-resizer:hover, .sidebar-resizer:active, .sidebar-resizer:focus, @@ -474,7 +483,7 @@ img { .sidebar-resizer.active { /* make the resize tool bigger when actually resizing, to avoid :hover styles on other stuff - while resizing */ + while resizing */ padding: 0 140px; width: 2px; margin-left: -140px; @@ -503,6 +512,7 @@ img { .src-sidebar-expanded .src .sidebar { overflow-y: auto; flex-basis: var(--src-sidebar-width); + width: var(--src-sidebar-width); } .src-sidebar-expanded .src .sidebar > *:not(#src-sidebar-toggle) { diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index e1d674e7d0406..be314317209a7 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -1337,6 +1337,13 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ // it gets the sidebar to restore its size. let desiredSidebarSize = null; + // Sidebar resize debouncer. + // + // The sidebar itself is resized instantly, but the body HTML can be too + // big for that, causing reflow jank. To reduce this, we queue up a separate + // animation frame and throttle it. + let pendingSidebarResizingFrame = false; + // If this page has no sidebar at all, bail out. const resizer = document.querySelector(".sidebar-resizer"); const sidebar = document.querySelector(".sidebar"); @@ -1360,12 +1367,26 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ if (isSrcPage) { window.rustdocCloseSourceSidebar(); updateLocalStorage("src-sidebar-width", null); + // [RUSTDOCIMPL] CSS variable fast path + // + // The sidebar width variable is attached to the element by + // storage.js, because the sidebar and resizer don't exist yet. + // But the resize code, in `resize()`, sets the property on the + // sidebar and resizer elements (which are the only elements that + // use the variable) to avoid recalculating CSS on the entire + // document on every frame. + // + // So, to clear it, we need to clear all three. document.documentElement.style.removeProperty("--src-sidebar-width"); + sidebar.style.removeProperty("--src-sidebar-width"); + resizer.style.removeProperty("--src-sidebar-width"); } else { addClass(document.documentElement, "hide-sidebar"); updateLocalStorage("hide-sidebar", "true"); updateLocalStorage("desktop-sidebar-width", null); document.documentElement.style.removeProperty("--desktop-sidebar-width"); + sidebar.style.removeProperty("--desktop-sidebar-width"); + resizer.style.removeProperty("--desktop-sidebar-width"); } } @@ -1384,15 +1405,27 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ } } - // Call this to set the correct CSS variable and setting. - // This function doesn't enforce size constraints. Do that before calling it! + /** + * Call this to set the correct CSS variable and setting. + * This function doesn't enforce size constraints. Do that before calling it! + * + * @param {number} size - CSS px width of the sidebar. + */ function changeSidebarSize(size) { if (isSrcPage) { updateLocalStorage("src-sidebar-width", size); - document.documentElement.style.setProperty("--src-sidebar-width", size + "px"); + // [RUSTDOCIMPL] CSS variable fast path + // + // While this property is set on the HTML element at load time, + // because the sidebar isn't actually loaded yet, + // we scope this update to the sidebar to avoid hitting a slow + // path in WebKit. + sidebar.style.setProperty("--src-sidebar-width", size + "px"); + resizer.style.setProperty("--src-sidebar-width", size + "px"); } else { updateLocalStorage("desktop-sidebar-width", size); - document.documentElement.style.setProperty("--desktop-sidebar-width", size + "px"); + sidebar.style.setProperty("--desktop-sidebar-width", size + "px"); + resizer.style.setProperty("--desktop-sidebar-width", size + "px"); } } @@ -1424,6 +1457,19 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ const constrainedPos = Math.min(pos, window.innerWidth - BODY_MIN, SIDEBAR_MAX); changeSidebarSize(constrainedPos); desiredSidebarSize = constrainedPos; + if (pendingSidebarResizingFrame !== false) { + clearTimeout(pendingSidebarResizingFrame); + } + pendingSidebarResizingFrame = setTimeout(() => { + if (currentPointerId === null || pendingSidebarResizingFrame === false) { + return; + } + pendingSidebarResizingFrame = false; + document.documentElement.style.setProperty( + "--resizing-sidebar-width", + desiredSidebarSize + "px" + ); + }, 100); } } // Respond to the window resize event. @@ -1447,6 +1493,7 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ window.removeEventListener("pointermove", resize, false); window.removeEventListener("pointerup", stopResize, false); removeClass(document.documentElement, "sidebar-resizing"); + document.documentElement.style.removeProperty( "--resizing-sidebar-width"); if (resizer.releasePointerCapture) { resizer.releasePointerCapture(currentPointerId); currentPointerId = null; @@ -1472,6 +1519,8 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ window.addEventListener("pointerup", stopResize, false); addClass(resizer, "active"); addClass(document.documentElement, "sidebar-resizing"); + const pos = e.clientX - sidebar.offsetLeft - 3; + document.documentElement.style.setProperty( "--resizing-sidebar-width", pos + "px"); desiredSidebarSize = null; } resizer.addEventListener("pointerdown", initResize, false); diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs index 080958b1cbcd2..ca9a78f51b38e 100644 --- a/src/librustdoc/html/static_files.rs +++ b/src/librustdoc/html/static_files.rs @@ -100,7 +100,6 @@ static_files! { storage_js => "static/js/storage.js", scrape_examples_js => "static/js/scrape-examples.js", wheel_svg => "static/images/wheel.svg", - sidebar_svg => "static/images/sidebar.svg", clipboard_svg => "static/images/clipboard.svg", copyright => "static/COPYRIGHT.txt", license_apache => "static/LICENSE-APACHE.txt",