Skip to content

fix: single scrollbar for RTD example pages#850

Open
blooop wants to merge 16 commits into
mainfrom
fix/rtd-single-scrollbar
Open

fix: single scrollbar for RTD example pages#850
blooop wants to merge 16 commits into
mainfrom
fix/rtd-single-scrollbar

Conversation

@blooop
Copy link
Copy Markdown
Owner

@blooop blooop commented Mar 26, 2026

Summary

  • Auto-resize report iframes to their content height so code and output flow as one document with a single page scrollbar
  • Add docs/_static/iframe-resize.js that targets iframe.bencher-report elements, reads contentDocument.scrollHeight, and uses ResizeObserver for async Bokeh/Panel rendering
  • Update RST template to remove fixed height:800px, add scrolling="no" and class="bencher-report"
  • Update multi-tab report inner iframe to use content-based sizing (falls back to viewport fill for standalone viewing)
  • Add CSS fallback min-height: 400px for graceful degradation without JS

Test plan

  • pixi run ci passes
  • RTD build: verify single scrollbar on example pages (code + output scroll together)
  • Multi-tab examples: verify height adjusts on tab switch
  • Standalone report viewing still works normally

🤖 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:

  • Add automatic height resizing for embedded bencher report iframes in Sphinx docs using a shared iframe-resize script.
  • Introduce collapsible source code sections for examples, allowing code to be toggled independently of the rendered report.

Bug Fixes:

  • Ensure multi-tab benchmark reports adjust iframe height to their inner content so tab switches no longer cause double scrollbars or clipped content.
  • Force the RTD content area to use a single main scrollbar instead of nested scrolling regions for example pages.

Enhancements:

  • Skip thumbnail generation on Read the Docs builds to reduce build times while retaining thumbnails for local docs generation.
  • Refine docs styling for code blocks and iframes, including improved line wrapping and full-width layout for example content.
  • Add dedicated Pixi docs environment and helper commands for building and serving documentation locally.

Build:

  • Extend project dependencies with a docs feature set, including Sphinx, RTD theme, and related tooling for documentation builds.

Documentation:

  • Wire the new iframe-resize JavaScript into the Sphinx docs build so example report iframes are auto-resized in generated documentation.

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
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Mar 26, 2026

Reviewer's Guide

Implements 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 pages

sequenceDiagram
    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
Loading

File-Level Changes

Change Details Files
Auto-resize Bencher report iframes in Sphinx docs using a dedicated JS helper and CSS, so example pages use a single scrollbar with content-based iframe height.
  • Add docs/_static/iframe-resize.js to compute iframe height from contentDocument scrollHeight, hide inner scrollbars, and use ResizeObserver/timeout retries for async-rendered content and nested multi-tab iframes.
  • Register iframe-resize.js in docs/conf.py via html_js_files so it loads on all doc pages.
  • Adjust docs/_static/custom.css to make .wy-nav-content full-height with a single page scrollbar and to style iframe.bencher-report with 100% width, min-height, no border, and hidden overflow as a JS fallback.
  • Change generated example RST iframe markup to use class="bencher-report", scrolling="no", and a content-based layout style instead of fixed height:800px.
docs/_static/iframe-resize.js
docs/conf.py
docs/_static/custom.css
bencher/example/meta/generate_examples.py
Improve example RST structure and code presentation with collapsible source blocks and better code wrapping.
  • Wrap each example’s literalinclude in a HTML
    with a Source Code so source can be toggled open/closed.
  • Add CSS rules for details.bencher-source and its summary to make the toggle look like a styled header and integrate with highlighted Python blocks.
  • Broaden code block wrapping CSS to .rst-content pre with !important so long lines wrap instead of forcing horizontal scrolling.
bencher/example/meta/generate_examples.py
docs/_static/custom.css
Make multi-tab report index pages auto-height based on inner iframe content while retaining viewport-based fallback for standalone viewing.
  • Update _write_iframe_index in bencher/bench_report.py so the outer iframe#content uses scrolling="no" and inline overflow:hidden, and is resized based on its contentDocument’s scrollHeight.
  • Introduce resizeInner, debouncedResize, and setupObserver helpers that set overflow:hidden on inner document, use ResizeObserver on the inner body, and debounce resize events to avoid thrashing.
  • Wire up load and resize events with several delayed resizeInner calls to handle async Bokeh/Panel rendering and tab switches while still allowing a viewport-height fallback when needed.
bencher/bench_report.py
Optimize docs/example generation for RTD and local docs work by making thumbnail creation optional and defining a dedicated docs environment and tasks.
  • Extend run_example_and_save with a skip_thumbnails flag and gate the playwright-based thumbnail creation behind this flag, logging when thumbnails are skipped while still returning timing info.
  • Detect READTHEDOCS in generate_all to set skip_thumbnails=True on RTD, skip Playwright browser startup there, and pass the flag through to run_example_and_save; otherwise retain existing thumbnail behavior.
  • Add a docs feature group in pyproject.toml with Sphinx, RTD theme, AutoAPI, Playwright, and Pillow, and define a docs Pixi environment that enables test, rerun, and docs features.
  • Introduce docs-quick and docs-serve Pixi tasks for fast rebuilds (without regenerating examples) and local HTTP serving of docs, documenting that these require the docs environment.
bencher/example/meta/generate_examples.py
pyproject.toml
pixi.lock

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions
Copy link
Copy Markdown

Performance Report for 68711c3

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

Full report

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
@github-actions
Copy link
Copy Markdown

Performance Report for 627e01d

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

Full report

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.
@github-actions
Copy link
Copy Markdown

Performance Report for 58c35f6

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

Full report

Updated by Performance Tracking workflow

blooop added 2 commits March 30, 2026 16:39
- 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
@github-actions
Copy link
Copy Markdown

Performance Report for ca44d80

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

Full report

Updated by Performance Tracking workflow

blooop added 2 commits March 31, 2026 19:31
- 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
@github-actions
Copy link
Copy Markdown

Performance Report for 92b3df2

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

Full report

Updated by Performance Tracking workflow

Detect READTHEDOCS env var and skip playwright browser launch and
thumbnail generation, keeping docs builds within time limits.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 2, 2026

Performance Report for f921591

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

Full report

Updated by Performance Tracking workflow

Resolve conflict in generate_examples.py: combine skip_thumbnails guard
from this branch with perf timing instrumentation from main.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 4, 2026

Performance Report for 2c95ac4

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

Full report

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
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 4, 2026

Performance Report for 93471ce

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

Full report

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.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 4, 2026

Performance Report for 20a1839

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

Full report

Updated by Performance Tracking workflow

@blooop blooop marked this pull request as ready for review April 4, 2026 15:01
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • 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.
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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread bencher/bench_report.py
window.addEventListener('resize', resizeIframe);
document.getElementById('content').addEventListener('load', resizeIframe);
resizeIframe();
var _resizeTimer = null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

<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).

"use strict";

var _heights = new WeakMap();
var _timer = null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

blooop added 2 commits April 4, 2026 15:04
… 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)
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 4, 2026

Performance Report for 4671a0a

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

Full report

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.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 4, 2026

Performance Report for 7a0c020

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

Full report

Updated by Performance Tracking workflow

blooop added 2 commits April 4, 2026 16:14
Below 50% zoom, content becomes unreadable. Clamp at 0.5 and let the
browser's native horizontal scroll handle the remainder.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant