fix: single scrollbar for RTD example pages#850
Conversation
Auto-resize report iframes to their content height so the code and output flow as one document with a single scrollbar instead of scrolling independently. - Add docs/_static/iframe-resize.js to auto-size iframe.bencher-report elements using contentDocument.scrollHeight + ResizeObserver - Update RST template: remove fixed 800px height, add scrolling="no" - Update multi-tab report inner iframe to use content-based sizing - Register iframe-resize.js in conf.py and add CSS fallback
Reviewer's GuideImplements automatic height resizing for embedded Bencher report iframes in the Sphinx/RTD docs (including multi-tab reports), adds collapsible source sections, and introduces a dedicated docs build environment with faster RTD-friendly generation (skipping thumbnails on RTD). Sequence diagram for iframe auto-resizing on example pagessequenceDiagram
actor User
participant Browser
participant DocsPage as DocsPage_HTML
participant Iframe as OuterIframe_bencher_report
participant JS as IframeResizeJS
participant ReportDoc as ReportDocument
participant Inner as InnerIframe_content
User->>Browser: Open docs example page
Browser->>DocsPage: Load HTML (includes iframe.bencher-report)
DocsPage->>Browser: Load iframe-resize.js
Browser->>JS: Execute script on load
JS->>Browser: Add DOMContentLoaded listener
Browser-->>JS: DOMContentLoaded
JS->>DocsPage: initAll()
JS->>DocsPage: Query iframe.bencher-report
JS->>Iframe: Attach load listener
alt iframe content already loaded
JS->>Iframe: setupIframe(iframe)
end
Browser-->>Iframe: Load report src
Iframe-->>JS: load event
JS->>Iframe: setupIframe(iframe)
JS->>Iframe: resizeToContent(iframe)
JS->>ReportDoc: Access contentDocument/body
JS->>ReportDoc: Set overflow hidden on html/body
JS->>Iframe: Set style.height = scrollHeight
JS->>Iframe: observeContent(iframe)
JS->>ReportDoc: new ResizeObserver(callback).observe(body)
loop Async content renders
ReportDoc-->>JS: ResizeObserver callback
JS->>Iframe: debouncedResize(iframe)
JS->>Iframe: resizeToContent(iframe)
JS->>Iframe: Update height if needed
end
opt Multi-tab report inside outer iframe
JS->>ReportDoc: Query inner iframe#content
ReportDoc-->>JS: inner iframe found
JS->>Inner: Add load listener
Inner-->>JS: load after tab switch
JS->>Iframe: resizeToContent(outer iframe) with retries
end
User->>Browser: Scroll page
Browser->>User: Single scrollbar for code + report
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Performance Report for
|
| Metric | Value |
|---|---|
| Total tests | 974 |
| Total time | 78.12s |
| Mean | 0.0802s |
| Median | 0.0020s |
Top 10 slowest tests
| Test | Time (s) |
|---|---|
test.test_bench_examples.TestBenchExamples::test_example_meta |
20.897 |
test.test_over_time_save_perf::test_save_faster_without_aggregated_tab |
4.619 |
test.test_hash_persistent.TestCrossProcessDeterminism::test_hash_stable_across_two_processes[ResultBool] |
3.678 |
test.test_generated_examples::test_generated_example[result_types/result_image/result_image_to_video.py] |
3.292 |
test.test_over_time_repeats.TestMaxSliderPoints::test_default_subsampling_caps_at_max |
1.770 |
test.test_optuna_result.TestOptunaResult::test_collect_optuna_plots_with_repeats |
0.963 |
test.test_bencher.TestBencher::test_bench_cfg_hash |
0.867 |
test.test_over_time_repeats.TestMaxSliderPoints::test_slider_subsampled |
0.810 |
test.test_result_bool.TestVolumeResult::test_volume_3float_multi_repeat |
0.763 |
test.test_bencher.TestBencher::test_combinations |
0.684 |
Updated by Performance Tracking workflow
- Reorder RST: show the report iframe first, then source code below - Wrap source code in a collapsible <details> section (closed by default) so the report is the primary visible content - Add pre-wrap CSS for code blocks to enable line wrapping
Performance Report for
|
| Metric | Value |
|---|---|
| Total tests | 974 |
| Total time | 84.28s |
| Mean | 0.0865s |
| Median | 0.0020s |
Top 10 slowest tests
| Test | Time (s) |
|---|---|
test.test_bench_examples.TestBenchExamples::test_example_meta |
22.619 |
test.test_over_time_save_perf::test_save_faster_without_aggregated_tab |
5.240 |
test.test_hash_persistent.TestCrossProcessDeterminism::test_hash_stable_across_two_processes[ResultBool] |
3.885 |
test.test_generated_examples::test_generated_example[result_types/result_image/result_image_to_video.py] |
2.907 |
test.test_over_time_repeats.TestMaxSliderPoints::test_default_subsampling_caps_at_max |
2.008 |
test.test_optuna_result.TestOptunaResult::test_collect_optuna_plots_with_repeats |
1.037 |
test.test_over_time_repeats.TestMaxSliderPoints::test_slider_subsampled |
0.898 |
test.test_result_bool.TestVolumeResult::test_volume_3float_multi_repeat |
0.866 |
test.test_bencher.TestBencher::test_bench_cfg_hash |
0.865 |
test.test_bencher.TestBencher::test_combinations |
0.837 |
Updated by Performance Tracking workflow
Use JS to break the iframe out of the RTD content pane by calculating its left offset and setting width to 100vw, so the report fills the entire browser width instead of being constrained by sidebar + padding.
Performance Report for
|
| Metric | Value |
|---|---|
| Total tests | 974 |
| Total time | 81.43s |
| Mean | 0.0836s |
| Median | 0.0020s |
Top 10 slowest tests
| Test | Time (s) |
|---|---|
test.test_bench_examples.TestBenchExamples::test_example_meta |
21.814 |
test.test_over_time_save_perf::test_save_faster_without_aggregated_tab |
5.190 |
test.test_hash_persistent.TestCrossProcessDeterminism::test_hash_stable_across_two_processes[ResultBool] |
3.762 |
test.test_generated_examples::test_generated_example[result_types/result_image/result_image_to_video.py] |
2.809 |
test.test_over_time_repeats.TestMaxSliderPoints::test_default_subsampling_caps_at_max |
1.943 |
test.test_optuna_result.TestOptunaResult::test_collect_optuna_plots_with_repeats |
1.024 |
test.test_over_time_repeats.TestMaxSliderPoints::test_slider_subsampled |
0.846 |
test.test_result_bool.TestVolumeResult::test_volume_3float_multi_repeat |
0.832 |
test.test_bencher.TestBencher::test_combinations |
0.817 |
test.test_bencher.TestBencher::test_bench_cfg_hash |
0.774 |
Updated by Performance Tracking workflow
- Add width handling: iframes now expand to content scrollWidth, not just height, so wide reports aren't clipped - Remove makeFullWidth hack that used 100vw positioning (prevented parent container from scrolling to reveal wide content) - Remove viewport-size fallback in inner iframe resize (was constraining to viewport instead of expanding to content) - Add postMessage bridge so inner iframe dimension changes propagate to outer iframe resize - Change overflow-x from always-visible scroll to auto (only when needed)
- Resolve merge conflict in custom.css (keep single-scrollbar approach) - Show source code expanded by default (open details element) - Move source code above the report iframe
Performance Report for
|
| Metric | Value |
|---|---|
| Total tests | 1128 |
| Total time | 107.02s |
| Mean | 0.0949s |
| Median | 0.0010s |
Top 10 slowest tests
| Test | Time (s) |
|---|---|
test.test_bench_examples.TestBenchExamples::test_example_meta |
23.063 |
test.test_over_time_save_perf::test_save_faster_without_aggregated_tab |
5.374 |
test.test_hash_persistent.TestCrossProcessDeterminism::test_hash_stable_across_two_processes[ResultBool] |
3.907 |
test.test_generated_examples::test_generated_example[result_types/result_image/result_image_to_video.py] |
3.024 |
test.test_over_time_repeats.TestMaxSliderPoints::test_default_subsampling_caps_at_max |
2.981 |
test.test_generated_examples::test_generated_example[advanced/advanced_cartesian_animation.py] |
2.954 |
test.test_over_time_repeats.TestMaxSliderPoints::test_slider_subsampled |
1.348 |
test.test_optuna_result.TestOptunaResult::test_collect_optuna_plots_with_repeats |
1.332 |
test.test_bencher.TestBencher::test_combinations_over_time |
1.250 |
test.test_bencher.TestBencher::test_combinations |
1.087 |
Updated by Performance Tracking workflow
- Add height dedup check to prevent no-op resizes - Debounce ResizeObserver callbacks (100ms) to prevent tight loops - Remove postMessage cross-frame feedback that could cause infinite resize - Merge main to pick up latest fixes
Performance Report for
|
| Metric | Value |
|---|---|
| Total tests | 1136 |
| Total time | 97.45s |
| Mean | 0.0858s |
| Median | 0.0010s |
Top 10 slowest tests
| Test | Time (s) |
|---|---|
test.test_bench_examples.TestBenchExamples::test_example_meta |
21.734 |
test.test_over_time_save_perf::test_save_faster_without_aggregated_tab |
4.826 |
test.test_hash_persistent.TestCrossProcessDeterminism::test_hash_stable_across_two_processes[ResultBool] |
3.374 |
test.test_generated_examples::test_generated_example[result_types/result_image/result_image_to_video.py] |
3.249 |
test.test_generated_examples::test_generated_example[cartesian_animation/cartesian_animation.py] |
2.825 |
test.test_over_time_repeats.TestMaxSliderPoints::test_default_subsampling_caps_at_max |
2.472 |
test.test_bencher.TestBencher::test_combinations_over_time |
1.064 |
test.test_bench_result_base.TestAggOverDimsStd::test_2d_agg_both_dims |
1.046 |
test.test_optuna_result.TestOptunaReportRouting::test_optuna_plots_per_sweep_tab |
1.002 |
test.test_bencher.TestBencher::test_combinations |
0.934 |
Updated by Performance Tracking workflow
Detect READTHEDOCS env var and skip playwright browser launch and thumbnail generation, keeping docs builds within time limits.
Performance Report for
|
| Metric | Value |
|---|---|
| Total tests | 1145 |
| Total time | 108.50s |
| Mean | 0.0948s |
| Median | 0.0020s |
Top 10 slowest tests
| Test | Time (s) |
|---|---|
test.test_bench_examples.TestBenchExamples::test_example_meta |
23.387 |
test.test_over_time_save_perf::test_save_faster_without_aggregated_tab |
5.634 |
test.test_hash_persistent.TestCrossProcessDeterminism::test_hash_stable_across_two_processes[ResultBool] |
3.906 |
test.test_generated_examples::test_generated_example[result_types/result_image/result_image_to_video.py] |
3.242 |
test.test_generated_examples::test_generated_example[cartesian_animation/cartesian_animation.py] |
3.069 |
test.test_over_time_repeats.TestMaxSliderPoints::test_default_subsampling_caps_at_max |
2.780 |
test.test_bencher.TestBencher::test_combinations_over_time |
1.399 |
test.test_bench_result_base.TestAggOverDimsStd::test_2d_agg_both_dims |
1.199 |
test.test_optuna_result.TestOptunaReportRouting::test_optuna_plots_per_sweep_tab |
1.131 |
test.test_over_time_repeats.TestShowAggregatedTimeTab::test_curve_aggregated_tab_absent_when_disabled |
1.088 |
Updated by Performance Tracking workflow
Resolve conflict in generate_examples.py: combine skip_thumbnails guard from this branch with perf timing instrumentation from main.
Performance Report for
|
| Metric | Value |
|---|---|
| Total tests | 1206 |
| Total time | 104.59s |
| Mean | 0.0867s |
| Median | 0.0020s |
Top 10 slowest tests
| Test | Time (s) |
|---|---|
test.test_bench_examples.TestBenchExamples::test_example_meta |
20.615 |
test.test_over_time_save_perf::test_save_faster_without_aggregated_tab |
5.069 |
test.test_hash_persistent.TestCrossProcessDeterminism::test_hash_stable_across_two_processes[ResultBool] |
3.792 |
test.test_generated_examples::test_generated_example[cartesian_animation/example_cartesian_animation.py] |
3.063 |
test.test_over_time_repeats.TestMaxSliderPoints::test_default_subsampling_caps_at_max |
3.051 |
test.test_generated_examples::test_generated_example[result_types/result_image/example_result_image_to_video.py] |
2.839 |
test.test_bencher.TestBencher::test_combinations_over_time |
1.480 |
test.test_bench_runner.TestBenchRunner::test_benchrunner_unified_interface |
1.060 |
test.test_optuna_result.TestOptunaResult::test_collect_optuna_plots_with_repeats |
1.040 |
test.test_over_time_repeats.TestCurveResultOverTime::test_curve_over_time_no_repeats |
1.009 |
Updated by Performance Tracking workflow
Separate docs deps (sphinx, myst-parser, etc.) into their own optional feature so the default dev environment stays lean. Add docs-quick and docs-serve tasks for fast local iteration. Usage: pixi run -e docs docs-serve
Performance Report for
|
| Metric | Value |
|---|---|
| Total tests | 1206 |
| Total time | 105.64s |
| Mean | 0.0876s |
| Median | 0.0020s |
Top 10 slowest tests
| Test | Time (s) |
|---|---|
test.test_bench_examples.TestBenchExamples::test_example_meta |
21.047 |
test.test_over_time_save_perf::test_save_faster_without_aggregated_tab |
5.154 |
test.test_hash_persistent.TestCrossProcessDeterminism::test_hash_stable_across_two_processes[ResultBool] |
3.890 |
test.test_over_time_repeats.TestMaxSliderPoints::test_default_subsampling_caps_at_max |
3.074 |
test.test_generated_examples::test_generated_example[cartesian_animation/example_cartesian_animation.py] |
3.042 |
test.test_generated_examples::test_generated_example[result_types/result_image/example_result_image_to_video.py] |
2.770 |
test.test_bencher.TestBencher::test_combinations_over_time |
1.491 |
test.test_optuna_result.TestOptunaResult::test_collect_optuna_plots_with_repeats |
1.042 |
test.test_over_time_repeats.TestCurveResultOverTime::test_curve_over_time_with_repeats |
0.971 |
test.test_over_time_repeats.TestMaxSliderPoints::test_no_subsampling_when_below_default_max |
0.936 |
Updated by Performance Tracking workflow
thumb_elapsed was unbound when skip_thumbnails=True (RTD builds), causing a NameError in the metadata dict. Initialize to 0.0 so the timing summary works in both paths.
Performance Report for
|
| Metric | Value |
|---|---|
| Total tests | 1206 |
| Total time | 104.11s |
| Mean | 0.0863s |
| Median | 0.0020s |
Top 10 slowest tests
| Test | Time (s) |
|---|---|
test.test_bench_examples.TestBenchExamples::test_example_meta |
20.675 |
test.test_over_time_save_perf::test_save_faster_without_aggregated_tab |
5.054 |
test.test_hash_persistent.TestCrossProcessDeterminism::test_hash_stable_across_two_processes[ResultBool] |
3.835 |
test.test_over_time_repeats.TestMaxSliderPoints::test_default_subsampling_caps_at_max |
3.078 |
test.test_generated_examples::test_generated_example[cartesian_animation/example_cartesian_animation.py] |
3.044 |
test.test_generated_examples::test_generated_example[result_types/result_image/example_result_image_to_video.py] |
2.796 |
test.test_bencher.TestBencher::test_combinations_over_time |
1.485 |
test.test_optuna_result.TestOptunaResult::test_collect_optuna_plots_with_repeats |
1.039 |
test.test_over_time_repeats.TestCurveResultOverTime::test_curve_over_time_no_repeats |
1.027 |
test.test_over_time_repeats.TestMaxSliderPoints::test_no_subsampling_when_below_default_max |
0.926 |
Updated by Performance Tracking workflow
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- In
docs/_static/iframe-resize.js, the debouncing uses a single_timerfor all iframes, which can cause resize events for one iframe to suppress updates for another; consider tracking timers per iframe or removing the shared debounce in favor of per-iframe scheduling. - Both the generated RST iframe markup and the
.bencher-reportCSS define width/min-height/border styles; consolidating these into the CSS class only would reduce duplication and the risk of the inline and stylesheet rules drifting out of sync. - The new multi-tab iframe index HTML and
iframe-resize.jsboth assumeResizeObserveris available; if you expect these pages to be viewed in older browsers, consider guarding its usage and falling back to simple load/timeout-based resizing when it’s missing.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `docs/_static/iframe-resize.js`, the debouncing uses a single `_timer` for all iframes, which can cause resize events for one iframe to suppress updates for another; consider tracking timers per iframe or removing the shared debounce in favor of per-iframe scheduling.
- Both the generated RST iframe markup and the `.bencher-report` CSS define width/min-height/border styles; consolidating these into the CSS class only would reduce duplication and the risk of the inline and stylesheet rules drifting out of sync.
- The new multi-tab iframe index HTML and `iframe-resize.js` both assume `ResizeObserver` is available; if you expect these pages to be viewed in older browsers, consider guarding its usage and falling back to simple load/timeout-based resizing when it’s missing.
## Individual Comments
### Comment 1
<location path="bencher/bench_report.py" line_range="256" />
<code_context>
-window.addEventListener('resize', resizeIframe);
-document.getElementById('content').addEventListener('load', resizeIframe);
-resizeIframe();
+var _resizeTimer = null;
+function debouncedResize() {{
+ if (_resizeTimer) return;
</code_context>
<issue_to_address>
**issue (complexity):** Consider simplifying the iframe resize logic by removing the debounce timer and extra delayed timeout while relying on `resizeInner` and `ResizeObserver` directly.
You can drop the debounce timer and one of the post-load timeouts without changing behavior in any meaningful way, since:
- `resizeInner` already guards on `_lastH`, so redundant calls are cheap.
- `ResizeObserver` callbacks can safely call `resizeInner` directly.
- Two delayed calls (500/1500 ms) are likely redundant; a single one is usually enough to catch late layout changes.
This removes mutable global timing state and reduces the number of timing mechanisms used.
```html
<script>
function switchTab(btn, src) {
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
document.getElementById('content').src = src;
}
var _lastH = 0;
function resizeInner() {
var f = document.getElementById('content');
try {
var doc = f.contentDocument || f.contentWindow.document;
if (doc && doc.body) {
doc.documentElement.style.overflow = 'hidden';
doc.body.style.overflow = 'hidden';
var h = Math.max(doc.documentElement.scrollHeight, doc.body.scrollHeight);
if (h > 0 && h !== _lastH) {
_lastH = h;
f.style.height = h + 'px';
}
}
} catch (e) {}
}
function setupObserver() {
var f = document.getElementById('content');
try {
var doc = f.contentDocument || f.contentWindow.document;
if (doc && doc.body) {
// Call resizeInner directly; _lastH prevents unnecessary DOM writes
new ResizeObserver(resizeInner).observe(doc.body);
}
} catch (e) {}
}
window.addEventListener('resize', resizeInner);
document.getElementById('content').addEventListener('load', function() {
resizeInner();
setupObserver();
// One delayed resize is usually enough to catch late layout changes
setTimeout(resizeInner, 500);
});
resizeInner();
</script>
```
This keeps all the current behavior (auto-height to content, ResizeObserver, safety around cross-origin access) while:
- Removing `_resizeTimer` and `debouncedResize`.
- Reducing from two delayed `setTimeout` calls to one.
- Making the temporal behavior easier to reason about (load → immediate resize + observer + one delayed resize; window resize → direct resize).
</issue_to_address>
### Comment 2
<location path="docs/_static/iframe-resize.js" line_range="5" />
<code_context>
+ "use strict";
+
+ var _heights = new WeakMap();
+ var _timer = null;
+
+ function resizeToContent(iframe) {
</code_context>
<issue_to_address>
**issue (complexity):** Consider scoping the debounce timer per iframe and extracting the repeated resize timeouts into a helper to simplify state and reduce duplication.
You can reduce the moving parts without changing behavior by (1) scoping the debounce timer per iframe instead of globally, and (2) factoring the repeated timeout logic into a small helper.
### 1. Use a per-iframe debounce timer
This avoids the global `_timer` and makes the behavior easier to reason about when multiple iframes are present:
```js
function debouncedResize(iframe) {
if (iframe._resizeTimer) {
clearTimeout(iframe._resizeTimer);
}
iframe._resizeTimer = setTimeout(function () {
iframe._resizeTimer = null;
resizeToContent(iframe);
}, 100);
}
```
You can then remove the global `_timer` variable entirely.
### 2. Factor out repeated timeout scheduling
The same pattern of `setTimeout(... 500/1500/3000)` is used in two places. Extracting it into a helper keeps behavior intact while making intent clearer:
```js
function scheduleResizeRetries(iframe, delays) {
delays.forEach(function (ms) {
setTimeout(function () {
resizeToContent(iframe);
}, ms);
});
}
```
Use it in `setupIframe`:
```js
function setupIframe(iframe) {
resizeToContent(iframe);
observeContent(iframe);
scheduleResizeRetries(iframe, [500, 1500, 3000]);
}
```
And in the inner `iframe#content` load handler:
```js
if (inner) {
inner.addEventListener("load", function () {
scheduleResizeRetries(iframe, [200, 1000, 3000]);
});
}
```
This keeps all existing timeouts and ResizeObserver behavior, but flattens the call chain and reduces the amount of state and duplicated logic future readers need to track.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| window.addEventListener('resize', resizeIframe); | ||
| document.getElementById('content').addEventListener('load', resizeIframe); | ||
| resizeIframe(); | ||
| var _resizeTimer = null; |
There was a problem hiding this comment.
issue (complexity): Consider simplifying the iframe resize logic by removing the debounce timer and extra delayed timeout while relying on resizeInner and ResizeObserver directly.
You can drop the debounce timer and one of the post-load timeouts without changing behavior in any meaningful way, since:
resizeInneralready guards on_lastH, so redundant calls are cheap.ResizeObservercallbacks can safely callresizeInnerdirectly.- Two delayed calls (500/1500 ms) are likely redundant; a single one is usually enough to catch late layout changes.
This removes mutable global timing state and reduces the number of timing mechanisms used.
<script>
function switchTab(btn, src) {
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
document.getElementById('content').src = src;
}
var _lastH = 0;
function resizeInner() {
var f = document.getElementById('content');
try {
var doc = f.contentDocument || f.contentWindow.document;
if (doc && doc.body) {
doc.documentElement.style.overflow = 'hidden';
doc.body.style.overflow = 'hidden';
var h = Math.max(doc.documentElement.scrollHeight, doc.body.scrollHeight);
if (h > 0 && h !== _lastH) {
_lastH = h;
f.style.height = h + 'px';
}
}
} catch (e) {}
}
function setupObserver() {
var f = document.getElementById('content');
try {
var doc = f.contentDocument || f.contentWindow.document;
if (doc && doc.body) {
// Call resizeInner directly; _lastH prevents unnecessary DOM writes
new ResizeObserver(resizeInner).observe(doc.body);
}
} catch (e) {}
}
window.addEventListener('resize', resizeInner);
document.getElementById('content').addEventListener('load', function() {
resizeInner();
setupObserver();
// One delayed resize is usually enough to catch late layout changes
setTimeout(resizeInner, 500);
});
resizeInner();
</script>This keeps all the current behavior (auto-height to content, ResizeObserver, safety around cross-origin access) while:
- Removing
_resizeTimeranddebouncedResize. - Reducing from two delayed
setTimeoutcalls to one. - Making the temporal behavior easier to reason about (load → immediate resize + observer + one delayed resize; window resize → direct resize).
| "use strict"; | ||
|
|
||
| var _heights = new WeakMap(); | ||
| var _timer = null; |
There was a problem hiding this comment.
issue (complexity): Consider scoping the debounce timer per iframe and extracting the repeated resize timeouts into a helper to simplify state and reduce duplication.
You can reduce the moving parts without changing behavior by (1) scoping the debounce timer per iframe instead of globally, and (2) factoring the repeated timeout logic into a small helper.
1. Use a per-iframe debounce timer
This avoids the global _timer and makes the behavior easier to reason about when multiple iframes are present:
function debouncedResize(iframe) {
if (iframe._resizeTimer) {
clearTimeout(iframe._resizeTimer);
}
iframe._resizeTimer = setTimeout(function () {
iframe._resizeTimer = null;
resizeToContent(iframe);
}, 100);
}You can then remove the global _timer variable entirely.
2. Factor out repeated timeout scheduling
The same pattern of setTimeout(... 500/1500/3000) is used in two places. Extracting it into a helper keeps behavior intact while making intent clearer:
function scheduleResizeRetries(iframe, delays) {
delays.forEach(function (ms) {
setTimeout(function () {
resizeToContent(iframe);
}, ms);
});
}Use it in setupIframe:
function setupIframe(iframe) {
resizeToContent(iframe);
observeContent(iframe);
scheduleResizeRetries(iframe, [500, 1500, 3000]);
}And in the inner iframe#content load handler:
if (inner) {
inner.addEventListener("load", function () {
scheduleResizeRetries(iframe, [200, 1000, 3000]);
});
}This keeps all existing timeouts and ResizeObserver behavior, but flattens the call chain and reduces the amount of state and duplicated logic future readers need to track.
… thumbnails - Replace overflow:hidden with overflow-x:auto; overflow-y:hidden in all 5 locations (iframe-resize.js, custom.css, bench_report.py, RST template) - Remove scrolling="no" deprecated attribute from iframes - Simplify .wy-nav-content CSS (remove height:99vh constraint) - Restore thumbnail generation on RTD (remove READTHEDOCS skip guard)
Performance Report for
|
| Metric | Value |
|---|---|
| Total tests | 1206 |
| Total time | 106.44s |
| Mean | 0.0883s |
| Median | 0.0020s |
Top 10 slowest tests
| Test | Time (s) |
|---|---|
test.test_bench_examples.TestBenchExamples::test_example_meta |
21.136 |
test.test_over_time_save_perf::test_save_faster_without_aggregated_tab |
5.174 |
test.test_hash_persistent.TestCrossProcessDeterminism::test_hash_stable_across_two_processes[ResultBool] |
3.914 |
test.test_over_time_repeats.TestMaxSliderPoints::test_default_subsampling_caps_at_max |
3.150 |
test.test_generated_examples::test_generated_example[cartesian_animation/example_cartesian_animation.py] |
3.059 |
test.test_generated_examples::test_generated_example[result_types/result_image/example_result_image_to_video.py] |
2.807 |
test.test_bencher.TestBencher::test_combinations_over_time |
1.503 |
test.test_over_time_repeats.TestCurveResultOverTime::test_curve_over_time_no_repeats |
1.048 |
test.test_optuna_result.TestOptunaResult::test_collect_optuna_plots_with_repeats |
1.046 |
test.test_bencher.TestBencher::test_bench_cfg_hash |
0.963 |
Updated by Performance Tracking workflow
Use CSS zoom to shrink iframe content when it's wider than the available width. This avoids a horizontal scrollbar buried at the bottom of tall reports. The JS measures scrollWidth vs clientWidth and applies a zoom factor to the content body, then re-measures height.
Performance Report for
|
| Metric | Value |
|---|---|
| Total tests | 1206 |
| Total time | 106.32s |
| Mean | 0.0882s |
| Median | 0.0020s |
Top 10 slowest tests
| Test | Time (s) |
|---|---|
test.test_bench_examples.TestBenchExamples::test_example_meta |
21.332 |
test.test_over_time_save_perf::test_save_faster_without_aggregated_tab |
4.784 |
test.test_hash_persistent.TestCrossProcessDeterminism::test_hash_stable_across_two_processes[ResultBool] |
4.170 |
test.test_generated_examples::test_generated_example[result_types/result_image/example_result_image_to_video.py] |
3.692 |
test.test_over_time_repeats.TestMaxSliderPoints::test_default_subsampling_caps_at_max |
3.308 |
test.test_generated_examples::test_generated_example[cartesian_animation/example_cartesian_animation.py] |
2.933 |
test.test_bencher.TestBencher::test_combinations_over_time |
1.472 |
test.test_bencher.TestBencher::test_bench_cfg_hash |
1.351 |
test.test_optuna_result.TestOptunaResult::test_collect_optuna_plots_with_repeats |
1.318 |
test.test_over_time_repeats.TestCurveResultOverTime::test_curve_over_time_with_repeats |
1.103 |
Updated by Performance Tracking workflow
Below 50% zoom, content becomes unreadable. Clamp at 0.5 and let the browser's native horizontal scroll handle the remainder.
Summary
docs/_static/iframe-resize.jsthat targetsiframe.bencher-reportelements, readscontentDocument.scrollHeight, and usesResizeObserverfor async Bokeh/Panel renderingheight:800px, addscrolling="no"andclass="bencher-report"min-height: 400pxfor graceful degradation without JSTest plan
pixi run cipasses🤖 Generated with Claude Code
Summary by Sourcery
Improve Read the Docs example pages so embedded benchmark reports and multi-tab reports auto-resize to their content, producing a single page scrollbar and better code/output flow.
New Features:
Bug Fixes:
Enhancements:
Build:
Documentation: