Skip to content

Conversation

@three-water666
Copy link
Contributor

@three-water666 three-water666 commented Dec 10, 2025

Description

This PR fixes a SecurityError that occurs when using the Image component in sandbox environments like CodeSandbox or StackBlitz.

Related Issue

Fixes ant-design/ant-design#56111

Root Cause

When the component unmounts, it attempts to remove event listeners from window.top. In sandbox environments where the preview runs inside an iframe, accessing window.top (which is cross-origin) triggers a browser security restriction.

Solution

Wrap the window.top access in a try-catch block to safely handle the cross-origin error. This ensures the component cleans up gracefully without crashing the application.

Test

  • Added tests/crossOrigin.test.tsx to simulate the cross-origin access scenario and verify that the unmount process no longer throws an error.

Summary by CodeRabbit

发布说明

  • Bug Fixes

    • 改进了跨域iframe场景下的事件监听器清理机制,防止组件卸载时出现崩溃。
  • Tests

    • 新增跨域iframe环境下的功能测试,确保组件在受限iframe上下文中的稳定性。

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Dec 10, 2025

@three-water666 is attempting to deploy a commit to the React Component Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Dec 10, 2025

概述

修复了在跨域 iframe(如 CodeSandbox 沙盒环境)中访问 window.top 时触发 SecurityError 的问题。通过为 window.top 事件监听器的移除操作添加 try-catch 防护,阻止组件卸载时产生崩溃,同时保留原有的清理逻辑。新增测试验证了该场景下的容错行为。

变更详情

类型 / 文件 变更摘要
错误处理加固
src/hooks/useMouseEvent.ts
为 window.top 事件监听器移除操作添加 try-catch 包装,防止在跨域 iframe 中访问受限时触发 SecurityError,保留原有的窗口清理逻辑不变
测试验证
tests/crossOrigin.test.tsx
新增 Jest/React Testing Library 测试文件,模拟 window.top 访问抛出错误的场景,验证 Image 组件卸载时不会崩溃

代码审查工作量评估

🎯 2 (简单) | ⏱️ ~12 分钟

需要关注的点:

  • Try-catch 块中的空捕获处理是否符合项目的错误处理策略
  • 测试用例中对 window.top mock 的实现是否完整覆盖所有跨域场景
  • Istanbul ignore next 注释是否与项目的覆盖率目标一致

🐰 沙盒里的兔子跳又跳,
顶窗不在也不烦恼,
Try-catch 捕住每个错,
图片预览畅通宵,
跨域之墙已推倒!✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and concisely describes the main change: adding try-catch error handling for window.top access to prevent cross-origin errors.
Linked Issues check ✅ Passed The code changes successfully address the linked issue #56111 by wrapping window.top access in try-catch to handle SecurityError exceptions, enabling the Image component to function properly in sandbox environments.
Out of Scope Changes check ✅ Passed All changes are directly related to the linked issue: modifications to useMouseEvent.ts for try-catch protection and a new test file validating cross-origin scenarios.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @three-water666, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical "SecurityError" that occurs when the "Image" component attempts to clean up event listeners from "window.top" in cross-origin sandbox environments. By wrapping the "window.top" access in a "try-catch" block, the component can now gracefully handle security restrictions during unmounting, preventing application crashes and improving stability in diverse deployment contexts. A new test has been added to validate this behavior.

Highlights

  • Cross-Origin Error Fix: Implemented a "try-catch" block around "window.top" access in "useMouseEvent.ts" to prevent "SecurityError" when the "Image" component unmounts in sandboxed environments like CodeSandbox or StackBlitz.
  • Robust Event Listener Cleanup: Ensures that event listeners on "window.top" are removed gracefully, even if cross-origin access is denied, preventing application crashes during component unmounting.
  • New Test Case: Added "tests/crossOrigin.test.tsx" to specifically simulate the cross-origin "window.top" access scenario and verify that the fix prevents unmount-related crashes.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request correctly fixes a SecurityError that occurs in sandboxed environments by wrapping access to window.top within a try-catch block. The change is simple and effective. The newly added test case in tests/crossOrigin.test.tsx properly simulates the cross-origin error and verifies that the component unmounts without crashing. I have one suggestion to improve the test's robustness by ensuring the cleanup logic is always executed, which is a best practice for test isolation.

Comment on lines +19 to +58
// Try to define a property that throws when accessed
// Note: In some jsdom environments window.top might be non-configurable.
// If this fails, we might need a different strategy, but this is the standard way to mock cross-origin.
try {
Object.defineProperty(window, 'top', {
get: () => {
throw new DOMException('Permission denied to access property "removeEventListener" on cross-origin object', 'SecurityError');
},
configurable: true,
});
} catch (e) {
// Fallback: If we can't mock window.top, we skip this specific test implementation details
// and rely on the fact that we modified the source code.
// But let's try to verify if we can mock it.
console.warn('Could not mock window.top:', e);
return;
}

const { container, unmount } = render(
<Image
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
/>,
);

// Open preview to trigger the effect that binds events
fireEvent.click(container.querySelector('.rc-image'));
act(() => {
jest.runAllTimers();
});

// Unmount should trigger the cleanup function where the crash happened
expect(() => {
unmount();
}).not.toThrow();

// Restore window.top
Object.defineProperty(window, 'top', {
value: originalTop,
configurable: true,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

To ensure that window.top is restored even if an assertion within the test fails, it's a best practice to place the cleanup logic in a finally block. This guarantees that the original state is restored, improving test isolation and preventing potential side effects on other tests.

    try {
      // Try to define a property that throws when accessed
      // Note: In some jsdom environments window.top might be non-configurable.
      // If this fails, we might need a different strategy, but this is the standard way to mock cross-origin.
      try {
        Object.defineProperty(window, 'top', {
          get: () => {
            throw new DOMException('Permission denied to access property "removeEventListener" on cross-origin object', 'SecurityError');
          },
          configurable: true,
        });
      } catch (e) {
        // Fallback: If we can't mock window.top, we skip this specific test implementation details
        // and rely on the fact that we modified the source code.
        // But let's try to verify if we can mock it.
        console.warn('Could not mock window.top:', e);
        return;
      }

      const { container, unmount } = render(
        <Image
          src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
        />,
      );

      // Open preview to trigger the effect that binds events
      fireEvent.click(container.querySelector('.rc-image'));
      act(() => {
        jest.runAllTimers();
      });

      // Unmount should trigger the cleanup function where the crash happened
      expect(() => {
        unmount();
      }).not.toThrow();
    } finally {
      // Restore window.top
      Object.defineProperty(window, 'top', {
        value: originalTop,
        configurable: true,
      });
    }

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/hooks/useMouseEvent.ts (1)

121-127: 卸载阶段对 window.top 的防御性清理已解决跨域崩溃问题,可细化异常处理与注释

这里用 try { window.top?.removeEventListener(...) } catch {} 可以有效防止 iframe 跨域环境下的 SecurityError 把卸载流程搞崩,和 PR 目标高度契合。不过有两个小点可以考虑优化:

  • catch (error) 现在完全静默,会把所有类型的异常都吞掉,包括可能的真实逻辑错误。可以考虑只对跨域场景“特赦”,例如判断 error instanceof DOMException && error.name === 'SecurityError' 时才静默,其余情况用 warning(false, ...) 记一条日志,排查会更容易。
  • 卸载时无条件尝试从 window.top 移除监听,而挂载时只有在 window.top !== window.self 时才注册。虽然 removeEventListener 是幂等的,这么做在行为上是安全的,但建议在 try 上方补一句注释说明“删除监听是幂等的,即使没注册也安全”,后续维护者更容易理解这里的意图。
tests/crossOrigin.test.tsx (1)

15-58: window.top mock 的测试场景覆盖到位,但可以提升健壮性与信号质量

整体思路很好:通过 Object.defineProperty(window, 'top', { get: () => { throw new DOMException(...) } }) 人为制造跨域访问异常,再验证 unmount() 不再抛错,和源码修改高度对齐。不过有两点小建议:

  • 行 22-35:如果 Object.defineProperty 抛错(例如某些环境里 window.top 不可配置),当前做法是 console.warn 后直接 return,这个 it 会被标记为“通过”,但实际上没有真正验证跨域场景,相当于潜在“假绿”。更稳妥的做法是:要么让测试在这种环境下显式失败(方便及早发现 CI 环境不支持该用例),要么在注释中明确说明这里接受“无法模拟则放弃校验”的权衡,以免后续读代码时误解测试的有效性。
  • 行 49-58:unmount() 包在 expect(() => { unmount(); }).not.toThrow() 里,如果未来又出现回归导致这里抛异常,后续恢复 window.top 的代码不会执行,可能污染后续测试环境。可以考虑把“记录原始 window.top + mock + 还原”这套逻辑用 try/finally 包起来,确保无论断言是否失败都能恢复全局状态。

这些都是测试层面的健壮性优化,不影响当前修复的正确性,可以视时间情况择机调整。

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 37e624a and c02804c.

📒 Files selected for processing (2)
  • src/hooks/useMouseEvent.ts (1 hunks)
  • tests/crossOrigin.test.tsx (1 hunks)

@codecov
Copy link

codecov bot commented Dec 10, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.41%. Comparing base (37e624a) to head (c02804c).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #488      +/-   ##
==========================================
- Coverage   99.41%   99.41%   -0.01%     
==========================================
  Files          17       17              
  Lines         511      509       -2     
  Branches      152      152              
==========================================
- Hits          508      506       -2     
  Misses          3        3              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@afc163 afc163 merged commit ea77092 into react-component:master Dec 10, 2025
7 of 8 checks passed
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.

CodeSandbox 沙盒环境 Image 点击预览报错

2 participants