Skip to content

[feature] Add st.iframe command#14433

Merged
lukasmasuch merged 29 commits intodevelopfrom
lukasmasuch/st-iframe
Mar 27, 2026
Merged

[feature] Add st.iframe command#14433
lukasmasuch merged 29 commits intodevelopfrom
lukasmasuch/st-iframe

Conversation

@lukasmasuch
Copy link
Copy Markdown
Collaborator

@lukasmasuch lukasmasuch commented Mar 20, 2026

Describe your changes

Implements st.iframe as specified in #14373, consolidating st.components.v1.iframe and st.components.v1.html into a single, discoverable command in the main Streamlit namespace.

Features:

  • Auto-detects input type: URL, Path object, existing file, or HTML string
  • Supports absolute URLs (http://, https://, data:), relative URLs (/app/static/...)
  • Embeds local HTML files via srcdoc, non-HTML files via media storage
  • width/height parameters with "stretch", "content", and pixel values
  • height="content" auto-sizes to content for srcdoc (falls back to 400px for URLs)
  • width="content" auto-sizes to content for srcdoc (falls back to stretch for URLs)
  • Uses standard widthConfig/heightConfig layout system (no custom proto fields)
  • tab_index parameter for accessibility/keyboard navigation

Usage:

import streamlit as st
from pathlib import Path

# Embed a URL
st.iframe("https://docs.streamlit.io", height=600)

# Embed HTML content with auto-sizing
st.iframe("<h1>Hello World</h1>")  # height="content" by default

# Embed a local HTML file
st.iframe(Path("reports/dashboard.html"), height=800)

# Embed a PDF
st.iframe(Path("documents/manual.pdf"), height=600)

# Auto-size both width and height
st.iframe("<div style='width:200px'>Content</div>", width="content", height="content")

GitHub Issue Link (if applicable)

Testing Plan

  • Unit Tests (Python) - 42 tests covering URL handling, HTML string detection, local file handling, dimension parameters, tab_index validation, width/height content sizing
  • Unit Tests (Frontend) - 27 tests covering rendering, auto-sizing behavior, message validation
  • E2E Tests (Playwright) - 10 tests covering rendering, content verification, sandbox policy, auto-sizing
  • Typing Tests (mypy) - Parameter and return type validation
Agent metrics
Type Name Count
skill checking-changes 6
skill debugging-streamlit 1
skill finalizing-pr 3
skill fixing-pr 2
skill implementing-feature 1
skill updating-internal-docs 4
subagent fixing-pr 6
subagent general-purpose 19
subagent reviewing-local-changes 4
subagent simplifying-local-changes 4

Implements st.iframe as specified in #14373, consolidating
st.components.v1.iframe and st.components.v1.html into a single,
discoverable command in the main namespace.

Features:
- Auto-detects input type: URL, Path object, existing file, or HTML string
- Supports absolute URLs (http/https/data:), relative URLs (/app/static/...)
- Embeds local HTML files via srcdoc, non-HTML files via media storage
- Width/height parameters with "stretch", "content", and pixel values
- tab_index parameter for accessibility/keyboard navigation

Closes #12977

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@lukasmasuch lukasmasuch added change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users labels Mar 20, 2026
Copilot AI review requested due to automatic review settings March 20, 2026 09:54
@lukasmasuch lukasmasuch added change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users ai-review If applied to PR or issue will run AI review workflow labels Mar 20, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 20, 2026

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-14433/streamlit-1.55.0-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-14433.streamlit.app (☁️ Deploy here if not accessible)

@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Mar 20, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new public st.iframe command to the main Streamlit namespace, consolidating existing iframe embedding behavior into a more discoverable API and extending it to handle URLs, HTML strings, and local files.

Changes:

  • Implement st.iframe in IframeMixin, including tab index validation and local file handling (HTML via srcdoc, non-HTML via media storage).
  • Export iframe from streamlit.__init__ so it’s available as st.iframe.
  • Add/extend Python unit tests and new Playwright E2E coverage for st.iframe.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
lib/streamlit/elements/iframe.py Adds st.iframe implementation, shared tab_index validation, and local file processing logic.
lib/streamlit/init.py Exposes st.iframe in the public Streamlit namespace.
lib/tests/streamlit/elements/iframe_test.py Refactors/extends unit tests for tab index validation and st.iframe behaviors (URL/HTML/local HTML).
e2e_playwright/st_iframe.py Adds an E2E example app exercising key st.iframe scenarios.
e2e_playwright/st_iframe_test.py Adds E2E tests for rendering, sandboxing, scrolling, and tab index behavior.

@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Mar 20, 2026
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ AI Review Failed

The AI review job failed to complete. Please check the workflow run for details.

You can retry by adding the 'ai-review' label again or manually triggering the workflow.

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

When using height='content' with HTML strings or local HTML files,
the iframe now automatically sizes to fit its content:

- Injects JavaScript into srcdoc content to measure and report height
- Frontend listens for postMessage and applies height dynamically
- URLs still fall back to 400px due to cross-origin restrictions

This enables a more natural user experience where iframe content
displays without unnecessary scrollbars or extra whitespace.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
lukasmasuch and others added 2 commits March 20, 2026 11:13
The previous height measurement used scrollHeight which rounds to
integers, but content can have fractional pixel heights. This caused
a tiny scrollbar to appear when content height was e.g. 194.4375px
but the iframe was sized to 194px.

Now using getBoundingClientRect().height with Math.ceil() to properly
round up and fully contain the content without scrollbars.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Add Final type annotations to module constants
- Consolidate duplicated frontend test cases for sandbox/feature policies
- Replace wait_for_timeout with proper expect assertion for height updates
- Fix misleading comments about iframe index and scrolling

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@lukasmasuch lukasmasuch added the ai-review If applied to PR or issue will run AI review workflow label Mar 20, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Mar 20, 2026
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ AI Review Failed

The AI review job failed to complete. Please check the workflow run for details.

You can retry by adding the 'ai-review' label again or manually triggering the workflow.

@lukasmasuch lukasmasuch added the update-snapshots Trigger snapshot autofix workflow label Mar 20, 2026
@github-actions github-actions bot removed the update-snapshots Trigger snapshot autofix workflow label Mar 20, 2026
lukasmasuch and others added 3 commits March 20, 2026 11:53
- Add iframe to element_mocks.py for public API tests
- Fix falsy check bug in IFrame.tsx (handle height=0)
- Add save_media_data call for caching compatibility
- Add unit test for non-HTML local file handling

Co-Authored-By: Claude Opus 4.6 <[email protected]>
## Describe your changes

Automated snapshot updates for #14433 created via the snapshot autofix
CI workflow.

This workflow was triggered by adding the `update-snapshots` label to a
PR after Playwright E2E tests failed with snapshot mismatches.

**Updated snapshots:** 1 file(s)

⚠️ **Please review the snapshot changes carefully** - they could mask
visual bugs if accepted blindly.

This PR targets a feature branch and can be merged without review
approval.

Co-authored-by: Streamlit Bot <[email protected]>
WebKit can be slower at processing postMessage from iframes.
Increase the timeout from default 5s to 10s to reduce flakiness.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@lukasmasuch lukasmasuch force-pushed the lukasmasuch/st-iframe branch from fb4ba39 to 21f20dd Compare March 20, 2026 12:53
WebKit's postMessage timing is inherently unreliable in CI environments,
causing the test_iframe_auto_sizing_height test to fail intermittently.
Skip this test on webkit following the pattern used by other similar tests.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The deprecation wrapper for components.v1.iframe changed the string
representation from a bound method to a wrapped function. Update the
tests to expect the new representation format.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@lukasmasuch lukasmasuch added the ai-review If applied to PR or issue will run AI review workflow label Mar 20, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Mar 20, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Summary

This PR introduces st.iframe as a new public API that consolidates st.components.v1.iframe and st.components.v1.html into a single, discoverable command in the main st namespace. Key capabilities include auto-detection of input type (URL, Path object, existing file, relative URL, or HTML string), auto-sizing height via height="content" using injected JavaScript and postMessage, local file embedding (HTML via srcdoc, non-HTML via media storage), and graceful deprecation of the legacy components APIs.

All three reviewers (claude-4.6-opus-high-thinking, gemini-3.1-pro, gpt-5.3-codex-high) completed their reviews and unanimously approved the PR.

Code Quality

Consensus: Well-structured, follows Streamlit patterns. All reviewers agree the implementation is clean and well-organized:

  • The backend IframeMixin cleanly separates input detection, proto construction, dimension handling, and local file processing.
  • The frontend useEffect for postMessage listening is properly scoped with cleanup, source validation, and height validation.
  • The deprecation utility additions (include_st_prefix, show_in_browser, show_once) are backward-compatible with sensible defaults.
  • The auto-height measurement script is comprehensive, using multiple measurement strategies (getBoundingClientRect, scrollHeight, offsetHeight) with MutationObserver for dynamic content and lastHeight deduplication.

One reviewer (gemini-3.1-pro) noted a minor robustness concern with _is_file() — it should guard against very long strings and ValueError from null bytes. This is covered in the inline comments.

Test Coverage

Consensus: Thorough and comprehensive. All reviewers praised the test coverage:

  • Python unit tests (40+ tests): Cover URL handling, HTML detection, local files (HTML, non-HTML, Path, .htm), dimensions, tab_index validation, height="content" fallback, and error paths.
  • Frontend unit tests: Cover useContentHeight behavior — listener registration/cleanup, valid/invalid height messages, source validation, and edge cases (NaN, Infinity, negative, zero).
  • E2E tests (9 tests): Cover rendering, HTML content, fixed dimensions, data URLs, scrolling, sandbox policy, tab index, CSS class, and auto-sizing height. Webkit is wisely skipped for postMessage timing flakiness.
  • Typing tests: iframe_types.py covers parameter combinations with assert_type.

One gap noted (gpt-5.3-codex-high, gemini-3.1-pro): No @pytest.mark.external_test coverage for the new iframe boundary behavior in externally hosted/embedded environments. See the external test recommendation below.

Backwards Compatibility

Consensus: Fully backward-compatible. All reviewers agree:

  • st.iframe is a purely additive new API — no existing APIs removed.
  • st.components.v1.html and st.components.v1.iframe continue to work, emitting console-only, show-once deprecation warnings.
  • The protobuf change (field #9 use_content_height) is additive — older frontends ignore it, older backends don't set it.
  • The deprecation_util changes use defaults preserving existing behavior.
  • E2E test updates correctly reflect the <function ...> repr from the deprecate_func_name wrapper.

Security & Risk

Consensus: Low risk, well-handled. All reviewers agree the security-relevant aspects are properly addressed:

  • The postMessage with '*' target origin is safe: it only runs inside sandboxed srcdoc content, the payload is only a height integer, and the frontend validates event.source === iframe.contentWindow.
  • The sandbox policy reuses DEFAULT_IFRAME_SANDBOX_POLICY, consistent with existing components.
  • Local file reading is consistent with how st.image, st.audio, etc., handle files.
  • Height values are validated as finite and non-negative before CSS application.
  • No new external dependencies, no changes to auth/XSRF/cookies/session management.

External test recommendation

Split opinion, leaning toward Yes (2 of 3 recommend):

  • claude-4.6-opus-high-thinking: No — the postMessage mechanism is entirely internal to st.iframe, not affecting Streamlit's own embedding behavior. High confidence.
  • gemini-3.1-pro: Yes — triggered categories: embedding/iframe boundary, static/component asset serving. Suggests testing auto-height in nested iframes and media file loading under different proxy/CORS rules. High confidence.
  • gpt-5.3-codex-high: Yes — same triggered categories. Suggests testing auto-resize in externally hosted embedded apps, media-backed file embedding behind proxies/base-paths, and behavior under stricter host-page CSP. Medium confidence.

Consolidated recommendation: Yes, external test coverage is recommended as a follow-up. While the postMessage mechanism is internal to st.iframe, the nested iframe scenario (Streamlit app itself embedded in a host page) creates a multi-level postMessage context worth validating. The media-backed file serving (/media/...) for non-HTML files behind external proxies is also a relevant concern. This does not need to block the merge but should be addressed soon after.

  • Triggered categories: 4 (Embedding and iframe boundary), 5 (Static and component asset serving)
  • Suggested focus areas: (1) height="content" auto-resize when app is embedded in a host-page iframe; (2) media-backed non-HTML file embedding behind external proxies/base-paths; (3) iframe behavior under stricter host-page CSP.
  • Confidence: Medium-High
  • Assumptions and gaps: Assessment is based on static diff review; no runtime execution in an external/embedded environment was performed.

Accessibility

Consensus: Appropriately handled.

  • The tab_index parameter maps correctly to the HTML tabindex attribute with proper validation (None, -1, 0, positive integers).
  • The iframe has title="st.iframe" for screen reader identification.
  • Keyboard navigation is properly controllable via tab_index.

One reviewer (gemini-3.1-pro) suggested allowing a custom title parameter in the future for better screen reader support — a reasonable enhancement for a follow-up.

Recommendations

  1. Harden _is_file(): Add a length guard before filesystem checks on long strings, and catch ValueError for null-byte strings (see inline comments).
  2. External test follow-up: Add at least one @pytest.mark.external_test scenario covering embedded-host auto-resize and media-file iframe behavior — either before merge or immediately after.
  3. Document Path() preference: Consider adding a note in the Parameters section recommending Path() objects for unambiguous file intent, since string-based _is_file() is CWD-dependent.
  4. E2E assertion style: Replace raw assert with expect-based assertions in E2E tests for consistency and auto-wait behavior (see inline comments).

Verdict

APPROVED: All three reviewers unanimously approved. The implementation is well-structured, thoroughly tested, backward-compatible, and security-conscious. The recommendations above are improvements, not blockers. The external test coverage is the most significant follow-up item but does not need to block the merge.


Consolidated AI review by claude-4.6-opus-high-thinking, synthesizing reviews from claude-4.6-opus-high-thinking, gemini-3.1-pro, and gpt-5.3-codex-high.

This review also includes 3 inline comment(s) on specific code lines.

lukasmasuch and others added 2 commits March 23, 2026 16:23
…st.iframe

- Remove custom `use_content_height` proto field in favor of standard
  `widthConfig` and `heightConfig` from the Element proto
- Add support for `width="content"` auto-sizing (falls back to stretch
  for cross-origin URLs)
- Rename auto-height script to auto-size script that reports both
  width and height via postMessage
- Update frontend to accept widthConfig/heightConfig props and apply
  measured dimensions for content sizing
- Update all tests (Python unit, frontend unit, E2E, typing)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Inline width == "content" and height == "content" comparisons since
they're only used once in the immediately following conditional.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@lukasmasuch lukasmasuch added the ai-review If applied to PR or issue will run AI review workflow label Mar 23, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Mar 23, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Summary

This PR introduces st.iframe, a new public Streamlit command that consolidates st.components.v1.iframe and st.components.v1.html into a single, discoverable API in the main st. namespace. Key capabilities include auto-detection of input type (URLs, Path objects, local files, raw HTML strings), content-based auto-sizing via injected JavaScript that measures and reports dimensions through postMessage, graceful fallbacks for URLs where cross-origin restrictions prevent measurement, and deprecation of the legacy APIs with console-only warnings shown once per message.

All three reviewers (claude-4.6-opus-high-thinking, gemini-3.1-pro, gpt-5.3-codex-high) completed their reviews successfully. No expected models failed.

Code Quality

Consensus: Strong — All three reviewers agreed the code is well-structured and follows Streamlit's established patterns.

  • Backend (lib/streamlit/elements/iframe.py): The IframeMixin.iframe() method cleanly separates input detection, proto population, and layout configuration. Helper functions (_is_file, _validate_tab_index, _inject_auto_size_script) are appropriately extracted. Local file processing handles both HTML and non-HTML files with proper error handling and uses the existing media file manager.
  • Frontend (frontend/lib/src/components/elements/IFrame/IFrame.tsx): The postMessage listener is correctly implemented as a useEffect (an external system subscription) with proper cleanup. The setContentDimensions updater uses referential equality checks to avoid unnecessary re-renders.
  • Deprecation utilities: The additions to deprecate_func_name (include_st_prefix, show_in_browser, show_once) are backwards-compatible with well-chosen defaults. The show_in_browser=False, show_once=True configuration is a thoughtful choice for soft deprecation.
  • Protobuf: Only adds field 9 to the reserved list — safe and follows proto guidance for released messages.

Test Coverage

Consensus: Comprehensive — All reviewers praised the test coverage across all layers.

  • Python unit tests (~42 tests): Cover URL handling, HTML string detection, local file handling (HTML and non-HTML), dimension parameters, tab_index validation, content sizing fallbacks, and error cases.
  • Frontend unit tests (~27 tests): Cover rendering, auto-sizing behavior (postMessage handling), source validation, invalid dimension rejection, event listener lifecycle, and config changes.
  • E2E tests (~10 tests): Cover rendering, content verification, sandbox/scrolling policies, tab_index, auto-sizing, and visual snapshots. WebKit is skipped for postMessage timing tests.
  • Typing tests: Verify parameter combinations via mypy assert_type.
  • Element mocks: st.iframe is added to the element mocks list for integration tests.

One reviewer (gpt-5.3-codex-high) noted a gap in @pytest.mark.external_test coverage. See the External Test Recommendation section for the consolidated assessment.

Backwards Compatibility

Consensus: Fully compatible — All three reviewers agreed this PR is fully backwards compatible.

  • st.components.v1.html() and st.components.v1.iframe() continue to work identically, now with soft deprecation warnings (console-only, shown once).
  • The internal _iframe() and _html() methods are unchanged.
  • The protobuf only adds field 9 to the reserved list.
  • New parameters in deprecate_func_name and show_deprecation_warning all have default values matching prior behavior.
  • st.iframe is a net-new addition to the st. namespace.

Security & Risk

Consensus: Low risk with appropriate guards — All reviewers confirmed the security model is sound.

  • postMessage handling: The auto-size script uses postMessage('*'), but this is confined to srcdoc iframes (same-origin, sandboxed) and only transmits integer dimensions. The frontend receiver validates event.source === iframeRef.current?.contentWindow and checks that dimensions are finite, non-negative numbers. One minor edge case (both event.source and contentWindow being null) is noted in inline comments.
  • Sandbox policy: Uses DEFAULT_IFRAME_SANDBOX_POLICY with allow-scripts and allow-same-origin, consistent with existing iframe behavior.
  • Local file reading: Consistent with existing Streamlit patterns (st.image, st.audio). Path traversal risk is limited since the server runs with the user's permissions.
  • Auto-size script injection: Only applies to srcdoc content; cannot be injected into external URL iframes.
  • No new dependencies introduced on either backend or frontend.

External test recommendation

Disagreement across reviewers:

  • claude-4.6-opus-high-thinking: No (high confidence) — changes are confined to a new element using existing proto, layout config, and media infrastructure. No changes to routing, auth, websocket, server endpoints, or embedding infrastructure.
  • gemini-3.1-pro: Yes — concerns about reverse proxies, CSP, and cross-origin embedding.
  • gpt-5.3-codex-high: Yes — concerns about embed boundary, static asset serving, and cross-origin behavior.

Consolidated assessment: No (blocking) / Yes (recommended follow-up)

The underlying infrastructure used by st.iframe — the iframe proto, sandbox policies, media file manager, and postMessage within srcdoc — is unchanged from existing behavior. The new code is a higher-level API over existing mechanisms. No server endpoints, routing, auth, or embedding framework code is modified. The postMessage auto-sizing is contained within same-origin srcdoc iframes and doesn't cross external boundaries. Therefore, missing external test coverage is not a blocking concern for this PR.

However, adding @pytest.mark.external_test coverage for iframe rendering and media URL behavior in hosted environments would be a valuable follow-up, particularly for:

  • height="content" / width="content" when the Streamlit app itself is embedded in a parent iframe.
  • Non-HTML local files rendered via media URLs (/media/...) in externally hosted deployments.

Accessibility

Consensus: Good — All reviewers confirmed accessibility is well-addressed.

  • The tab_index parameter is well-documented and follows web specifications. Valid values (-1, 0, positive integers) are correctly validated.
  • The title="st.iframe" attribute provides a consistent accessible name for screen readers.
  • The tabIndex attribute is correctly passed through to the rendered iframe element.
  • One reviewer (gemini-3.1-pro) suggested allowing developers to customize the title attribute for better screen reader support — a reasonable future enhancement.

Recommendations

  1. Address the event.source null guard in IFrame.tsx:82 — add a truthy check before comparing to prevent the edge case where both values are null. (See inline comment.)
  2. Consider adding a lenient WebKit assertion for auto-sizing tests to provide at least basic coverage across all browsers, rather than skipping entirely.
  3. Add @pytest.mark.external_test coverage as a follow-up for iframe rendering in hosted/embedded environments.
  4. Replace bare assert with Playwright's expect in E2E tests for consistency and reduced flakiness risk.
  5. Future enhancements to consider: customizable title attribute for screen readers, customizable sandbox parameter for advanced users, fast-path heuristic in _is_file to skip filesystem checks for obvious HTML strings.

Verdict

APPROVED: This is a well-designed, thoroughly tested new feature that follows Streamlit's established patterns. All three reviewers agreed the implementation is solid, the test coverage is comprehensive, and backwards compatibility is fully preserved. Two of three reviewers approved outright; the third requested changes primarily for external test coverage, which the consolidated assessment determines is a valuable follow-up but not a blocking concern. The inline comments note minor defensive improvements and best-practice alignments that can be addressed before or after merge.


This is a consolidated AI review by claude-4.6-opus-high-thinking, synthesizing reviews from claude-4.6-opus-high-thinking, gemini-3.1-pro, and gpt-5.3-codex-high.

This review also includes 7 inline comment(s) on specific code lines.

- Fix TypeScript type error by extracting validated dimensions to local
  variables before setState, allowing proper type narrowing
- Add truthy guard on event.source in postMessage handler
- Add document.body guard in auto-size script for malformed HTML
- Improve _is_file() with length guard and ValueError catch
- Update E2E tests to use expect assertions instead of bare assert
- Add comments explaining inline styles and effect behavior

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 23, 2026

🧪 Significant E2E test count increase detected

This PR has added 31 E2E test cases (threshold: 30)

  • Current PR: 5406 tests
  • Latest develop: 5375 tests

⚠️ Note: E2E tests are expensive to run. Please ensure you're following best practices:

  • Prefer aggregated scenario tests over many micro-tests
  • Add tests to existing files when they fit the scope
  • Test each aspect only once per browser run

@lukasmasuch lukasmasuch added the ai-review If applied to PR or issue will run AI review workflow label Mar 23, 2026
…spec

Update the st.iframe product spec to document that width="content" falls
back to "stretch" for URLs (in addition to the existing note about
height="content" falling back to 400px).

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Mar 23, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Summary

This PR introduces a new st.iframe command in the main Streamlit namespace, consolidating st.components.v1.iframe and st.components.v1.html into a unified, more discoverable API. Key capabilities include auto-detection of input type (URL, Path object, local file, or HTML string), content-based auto-sizing via injected JavaScript that measures dimensions and reports them via postMessage, and local file handling (HTML embedded via srcdoc, non-HTML uploaded to media storage). The old APIs are preserved with gentle, log-only deprecation warnings.

Reviewer consensus: All three reviewers (claude-4.6-opus-high-thinking, gemini-3.1-pro, gpt-5.3-codex-high) completed their reviews. Two approved; one requested changes citing implementation-level robustness concerns and missing external test coverage.

Code Quality

All three reviewers agreed the implementation is well-structured and follows established Streamlit patterns across backend, frontend, proto, and test layers. Specific points of agreement:

  • Backend: Clean separation of concerns with well-named module-level helpers (_is_file, _validate_tab_index, _inject_auto_size_script, _process_local_file). The input detection order is logical and documented.
  • Frontend: The useEffect for message handling follows React best practices with proper cleanup, source validation via event.source === iframeRef.current?.contentWindow, and a functional setState updater. The shouldMeasureContent derived value correctly gates both the effect and dimension style application.
  • Deprecation: The new show_in_browser=False and show_once=True options to deprecate_func_name provide a less intrusive deprecation path.
  • Proto: Minimal change—only adding reserved field 9.

The inline style prop on the iframe (line 146 of IFrame.tsx) technically conflicts with the frontend AGENTS.md guideline to avoid inline styles, but the code justifies this as necessary for dynamic pixel values received via postMessage. All reviewers accepted this as a valid exception.

Test Coverage

All three reviewers agreed test coverage is comprehensive:

  • Python unit tests (42 tests): URL handling, HTML string detection, local file handling, Path objects, dimension parameters, tab_index validation, content sizing fallbacks, and error cases.
  • Frontend unit tests (27 tests): Rendering, auto-sizing behavior, message validation (NaN, Infinity, negative), source validation, and config change handling.
  • E2E tests (10 tests): Full-stack rendering, content verification, sandbox policy, scrolling, tab index, auto-sizing, and snapshot tests.
  • Typing tests: Parameter and return type validation via mypy assert_type.
  • Deprecation tests: New coverage for show_in_browser, show_once, and include_st_prefix parameters.

The auto-sizing E2E tests appropriately skip WebKit (@pytest.mark.skip_browser("webkit")) due to postMessage timing flakiness in CI.

Backwards Compatibility

All reviewers confirmed no breaking changes:

  • st.components.v1.html and st.components.v1.iframe continue to work with log-only deprecation warnings (show_in_browser=False, show_once=True).
  • The new st.iframe introduces different defaults (e.g., height="content" instead of 150px, scrolling=True instead of False), which is appropriate for a new command.
  • Proto compatibility maintained—only reserved field 9 added.
  • Existing E2E tests updated to expect functools.wraps string representation changes.

Security & Risk

All three reviewers confirmed the postMessage security implementation is sound:

  • The auto-size script uses postMessage('*'), but this is safe because: (1) it only runs inside srcdoc (same-origin, sandboxed), (2) the payload contains only dimension integers, and (3) the frontend validates event.source === iframe.contentWindow.
  • The existing DEFAULT_IFRAME_SANDBOX_POLICY is unchanged.
  • Message validation on the frontend checks type, source, and dimension values (finite, non-negative).
  • Local file access via _process_local_file is consistent with Streamlit's trust model (other APIs like st.image, st.video have the same pattern).
  • No new backend dependencies or injection paths introduced.

External test recommendation

  • Recommend external_test: No
  • Triggered categories: 4 (Embedding and iframe boundary)
  • Evidence:
    • lib/streamlit/elements/iframe.py: Auto-size script uses window.parent.postMessage within srcdoc
    • frontend/lib/src/components/elements/IFrame/IFrame.tsx: New message event listener for iframe sizing
  • Confidence: High

Reviewer disagreement: gemini-3.1-pro and gpt-5.3-codex-high recommended external tests; claude-4.6-opus-high-thinking did not.

Resolution: The postMessage communication is strictly between the Streamlit app and its own srcdoc iframe (same-origin). It does not affect how Streamlit itself is embedded in host pages. No changes are made to Streamlit's iframe sandbox/allow policies, embedding behavior, host-to-guest communication, routing, auth, or session handling. The DEFAULT_IFRAME_SANDBOX_POLICY is unchanged. While verifying nested iframe behavior is a reasonable future enhancement, external test coverage is not warranted for this PR.

Accessibility

  • Tab index support: Properly implemented with validation for valid values (-1, 0, positive integers).
  • Title attribute: The iframe has title="st.iframe" for screen reader identification. gemini-3.1-pro suggested adding a user-configurable title parameter for better accessibility—this is a good enhancement to consider in a follow-up but not blocking.
  • Sandbox policy: Includes allow-same-origin and allow-scripts for functional content (unchanged from existing behavior).

Recommendations

  1. Consider resetting contentDimensions state when srcDoc changes to avoid a brief flash of stale dimensions (see inline comment on IFrame.tsx). This is low-risk but improves robustness.
  2. Consider adding a user-configurable title parameter to st.iframe in a follow-up PR, allowing users to provide descriptive iframe titles for screen readers (currently hardcoded to "st.iframe").
  3. The _shown_warnings set in deprecation_util.py is module-level and never cleared. This is fine for the intended use case (small, finite set of deprecation messages), but a brief comment documenting the lifetime assumption would help future maintainers.

Verdict

APPROVED: Well-implemented new st.iframe command with comprehensive test coverage, clean backwards compatibility via gentle deprecation, and proper security practices. The majority of reviewers approved, and the issues raised by the dissenting review are non-critical enhancements rather than blocking concerns. The one medium-severity item (stale content dimensions) is addressed via inline comment as a recommended improvement.


This is a consolidated AI review by claude-4.6-opus-high-thinking, synthesizing reviews from claude-4.6-opus-high-thinking, gemini-3.1-pro, and gpt-5.3-codex-high. Please verify the feedback and use your judgment.

This review also includes 3 inline comment(s) on specific code lines.

lukasmasuch and others added 12 commits March 23, 2026 19:21
Field 9 (use_content_height) was added and removed within the same PR,
so it was never shipped in a release and doesn't need to be reserved.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Change deprecation warnings for st.components.v1.html and
st.components.v1.iframe to log on every call instead of just once,
making it easier for developers to notice during debugging.

Also improve e2e test to use Playwright's expect assertion.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Move the auto-size measurement script from backend (Python) to frontend
(TypeScript) for better separation of concerns. The script and its
message listener are tightly coupled, so co-locating them in the
frontend makes the code easier to maintain and test.

- Add AUTO_SIZE_SCRIPT constant and injectAutoSizeScript() to IFrame.tsx
- Remove _AUTO_SIZE_SCRIPT and _inject_auto_size_script() from iframe.py
- Backend now passes raw srcdoc; frontend injects script when needed
- Add frontend tests for script injection behavior
- Update Python tests to verify raw srcdoc passthrough

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Remove unnecessary type assertion in IFrame.tsx (eslint)
- Fix E2E test to use direct assertion for tabindex absence
- Remove unused ty:ignore comment in st_table.py
- Add deprecation notices for components.v1.iframe and components.v1.html
- Add width/height props to StyledIframe for content-based sizing

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The iframe auto-sizing dimensions are set as HTML attributes (width, height)
on the iframe element, not as inline style properties. Updated the tests to
check the correct attributes.

Also fixed tabindex absence test to use expect() with regex for consistency.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Move JavaScript auto-size measurement script from Python backend to
  frontend IFrame component (single source of truth)
- Move URL height fallback logic to frontend, using 25rem instead of 400px
- Remove use_content_height proto field (frontend uses layout config)
- Simplify backend: just pass width/height through to layout config

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Strings starting with `/` are now always treated as relative URLs and
never checked against the filesystem. This prevents accidentally reading
filesystem data when the user intends a relative URL (e.g., for static
file serving). To read an absolute path from disk, use Path() explicitly.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Move mimetypes, caching, url_util, validate_height, validate_width,
and Path imports from inside functions to module-level imports.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The backend now correctly handles the fallback logic for height="content"
and width="content" when the source is a URL (not srcdoc):
- height="content" falls back to 400px for URLs
- width="content" falls back to "stretch" for URLs

Also fixes the detection order so that existing Unix file paths (starting
with /) are correctly identified as files before being treated as relative
URLs.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Remove underscore prefix from Path import now that the TYPE_CHECKING
import is no longer needed.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@lukasmasuch lukasmasuch merged commit 2aceb0e into develop Mar 27, 2026
44 checks passed
@lukasmasuch lukasmasuch deleted the lukasmasuch/st-iframe branch March 27, 2026 18:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add st.iframe command

3 participants