Skip to content

Fix(widget) clicked links in widget open new tab#15676

Open
47NoahThompson wants to merge 2 commits into
infiniflow:mainfrom
47NoahThompson:fix(widget)-Clicked-Links-in-widget-open-new-tab
Open

Fix(widget) clicked links in widget open new tab#15676
47NoahThompson wants to merge 2 commits into
infiniflow:mainfrom
47NoahThompson:fix(widget)-Clicked-Links-in-widget-open-new-tab

Conversation

@47NoahThompson
Copy link
Copy Markdown

@47NoahThompson 47NoahThompson commented Jun 4, 2026

What problem does this PR solve?

Ensure links clicked in the widget open in a new tab

Type of change

  • Bug Fix (non-breaking change which fixes an issue)
  • New Feature (non-breaking change which adds functionality)
  • Documentation Update
  • Refactoring
  • Performance Improvement
  • Other (please describe):

47NoahThompson and others added 2 commits June 4, 2026 10:23
Add support for emitting and handling CHAT_LINK_CLICK messages so links inside the embedded widget can be opened in a new tab or navigated in-place. Introduce newTabLink toggles (example and generated embed) and wire host/embed message handling. Refactor embed code generation (generateEmbedCode) and update handleOpenInNewTab to open a blob-based HTML preview for widget embeds (uses noopener,noreferrer). Update floating markdown: add remark-gfm/remark-math, remove replaceRetrievingToSection from the pipeline, simplify paragraph renderer, and override anchor rendering to post CHAT_LINK_CLICK back to the parent. Small logging and cleanup adjustments included.
@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Jun 4, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements a CHAT_LINK_CLICK postMessage protocol allowing embedded chat widgets to notify their parent applications when users click markdown links. The floating chat widget markdown renderer emits postMessages on link clicks, the embed dialog forwards these messages and regenerates standalone HTML for "Open in new tab" actions, and the demo page demonstrates consuming these events with configurable navigation behavior.

Changes

Chat Link Click Protocol and Integration

Layer / File(s) Summary
Markdown link click emission and plugin setup
web/src/components/floating-chat-widget-markdown.tsx
Removes replaceRetrievingToSection preprocessing and refactors to use explicit remarkGfm and remarkMath plugins. Introduces emitChatLinkClick(url) helper that posts CHAT_LINK_CLICK messages to parent window. Updates anchor element handler to intercept clicks, extract resolved URLs, and emit postMessage while preserving any provided onClick callback.
Embed dialog message forwarding and standalone HTML generation
web/src/components/embed-dialog/index.tsx
Adds generateEmbedCode callback hook to build embed HTML string. Extends inline widget script's message handler with newTabLink flag, message logging, and CHAT_LINK_CLICK forwarding logic. Refactors "Open in new tab": widget embeds now generate and open standalone HTML documents via Blob object URLs with noopener,noreferrer; non-widget embeds continue opening iframe URLs with timeout-based cleanup.
Demo page message consumption
example/chat_demo/widget_demo.html
Adds newTabLink boolean constant and implements CHAT_LINK_CLICK message handler that conditionally calls window.open(..., '_blank') for new-tab navigation or window.location.href for same-page navigation based on the flag.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • infiniflow/ragflow#14613: Modifies replaceRetrievingToSection rendering and <details class="retrieving"> handling in chat text, which directly relates to the removal of replaceRetrievingToSection preprocessing in this PR.
  • infiniflow/ragflow#14214: Centralizes remarkPlugins configuration via shared MarkdownRemarkPlugins in FloatingChatWidgetMarkdown, whereas this PR refactors to explicit plugin imports.

Suggested labels

🐞 bug, 🧰 typescript

Suggested reviewers

  • dcc123456
  • cike8899

Poem

🐇 Links now leap through windows wide,
PostMessages ride the tide,
Embeds generate with graceful flow,
Markdown clicks make conversations go,
Cross-frame harmony, pure and bright! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: enabling clicked links in the embedded widget to open in new tabs.
Description check ✅ Passed The description follows the template with both required sections completed: problem statement and type of change properly selected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

Copy link
Copy Markdown
Contributor

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/src/components/embed-dialog/index.tsx`:
- Line 253: Remove the debug console logging inside the message event handler
that currently calls console.log(e); locate the
window.addEventListener('message', ...) callback (or the message event handler
function) in the embed script and delete the console.log(e) call, or replace it
with a safe, environment-gated logger (e.g., only log in development via a debug
flag or use a secure logger method) so message payloads are not emitted to host
page browser consoles in production.
- Around line 266-269: In the postMessage handler's CHAT_LINK_CLICK branch (the
code that checks e.data.type==='CHAT_LINK_CLICK' and uses newTabLink), remove
the noisy console.log(e), validate and sanitize e.data.url by constructing a URL
and allowing only http: or https: protocols (reject javascript:, data:, about:
etc.), and then open it safely: if newTabLink call window.open(sanitizedUrl,
'_blank', 'noopener,noreferrer') and otherwise set window.location.href =
sanitizedUrl; if URL parsing/validation fails, do nothing or log a harmless
warning via process logger instead.

In `@web/src/components/floating-chat-widget-markdown.tsx`:
- Around line 315-327: The onClick handler for the custom anchor lets the
browser navigate before/while emitting CHAT_LINK_CLICK; update the handler in
floating-chat-widget-markdown.tsx (the onClick function that calls
onClick?.(event) and emitChatLinkClick(clickedUrl)) to call
event.preventDefault() when a clickedUrl is present (before emitChatLinkClick)
so the navigation is cancelled, then emit the event; keep calling the original
onClick?.(event) (either before or after preventDefault) to preserve existing
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cc0a3ee5-c8fd-43d7-b159-57b4c55b5cb8

📥 Commits

Reviewing files that changed from the base of the PR and between 96a4166 and 40f418c.

📒 Files selected for processing (3)
  • example/chat_demo/widget_demo.html
  • web/src/components/embed-dialog/index.tsx
  • web/src/components/floating-chat-widget-markdown.tsx

<script>
const newTabLink = true;
window.addEventListener('message',e=>{
console.log(e)
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove debug logging from the generated embed script.

Line 253 logs all message events and payloads in host pages; this can leak user/session data into browser logs and creates noise in production.

Suggested fix
-  console.log(e)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log(e)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/embed-dialog/index.tsx` at line 253, Remove the debug
console logging inside the message event handler that currently calls
console.log(e); locate the window.addEventListener('message', ...) callback (or
the message event handler function) in the embed script and delete the
console.log(e) call, or replace it with a safe, environment-gated logger (e.g.,
only log in development via a debug flag or use a secure logger method) so
message payloads are not emitted to host page browser consoles in production.

Comment on lines +266 to +269
else if(e.data.type==='CHAT_LINK_CLICK'&&e.data.url){
if(newTabLink)window.open(e.data.url,'_blank');
else window.location.href=e.data.url;
}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
file="web/src/components/embed-dialog/index.tsx"
# show line numbers around 240-330
python3 - <<'PY'
import itertools,sys
path="web/src/components/embed-dialog/index.tsx"
start=220; end=330
with open(path,'r',encoding='utf-8') as f:
    for i,line in enumerate(f,1):
        if start<=i<=end:
            print(f"{i:4d}:{line.rstrip()}")
PY
# also search for CHAT_LINK_CLICK occurrences and any window.open with noopener usage
rg -n "CHAT_LINK_CLICK" web/src/components/embed-dialog/index.tsx web/src/components/embed-dialog -S || true
rg -n "window\.open\(|noopener|noreferrer" web/src/components/embed-dialog/index.tsx -S || true
# find where newTabLink is defined and whether e.data.url is derived/validated earlier in the postMessage pipeline
rg -n "newTabLink" web/src/components/embed-dialog/index.tsx -n -S || true
rg -n "postMessage|e\.data\.url|data\.url" web/src/components/embed-dialog/index.tsx -S || true

Repository: infiniflow/ragflow

Length of output: 4989


🏁 Script executed:

#!/bin/bash
set -euo pipefail
file="web/src/components/embed-dialog/index.tsx"
# show line numbers around 240-330
python3 - <<'PY'
import itertools,sys
path="web/src/components/embed-dialog/index.tsx"
start=220; end=330
with open(path,'r',encoding='utf-8') as f:
    for i,line in enumerate(f,1):
        if start<=i<=end:
            print(f"{i:4d}:{line.rstrip()}")
PY
# also search for CHAT_LINK_CLICK occurrences and any window.open with noopener usage
rg -n "CHAT_LINK_CLICK" web/src/components/embed-dialog/index.tsx web/src/components/embed-dialog -S || true
rg -n "window\.open\(|noopener|noreferrer" web/src/components/embed-dialog/index.tsx -S || true
# find where newTabLink is defined and whether e.data.url is derived/validated earlier in the postMessage pipeline
rg -n "newTabLink" web/src/components/embed-dialog/index.tsx -n -S || true
rg -n "postMessage|e\.data\.url|data\.url" web/src/components/embed-dialog/index.tsx -S || true

Repository: infiniflow/ragflow

Length of output: 4989


Sanitize e.data.url in generated CHAT_LINK_CLICK handler and add noopener,noreferrer

  • web/src/components/embed-dialog/index.tsx generates a widget postMessage handler where CHAT_LINK_CLICK directly uses e.data.url in window.open(e.data.url,'_blank') (with newTabLink = true) and does not validate the URL protocol/format; also missing noopener,noreferrer, enabling tabnabbing and potential javascript:/data: navigation injection.
  • console.log(e) in the message handler adds noisy debug output for embedded widgets.
Suggested fix
-  else if(e.data.type==='CHAT_LINK_CLICK'&&e.data.url){
-    if(newTabLink)window.open(e.data.url,'_blank');
-    else window.location.href=e.data.url;
-  }
+  else if(e.data.type==='CHAT_LINK_CLICK'&&e.data.url){
+    let parsed;
+    try { parsed = new URL(e.data.url, window.location.href); } catch { return; }
+    if (!['http:', 'https:', 'mailto:', 'tel:'].includes(parsed.protocol)) return;
+    const safeUrl = parsed.toString();
+    if (newTabLink) window.open(safeUrl, '_blank', 'noopener,noreferrer');
+    else window.location.assign(safeUrl);
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
else if(e.data.type==='CHAT_LINK_CLICK'&&e.data.url){
if(newTabLink)window.open(e.data.url,'_blank');
else window.location.href=e.data.url;
}
else if(e.data.type==='CHAT_LINK_CLICK'&&e.data.url){
let parsed;
try { parsed = new URL(e.data.url, window.location.href); } catch { return; }
if (!['http:', 'https:', 'mailto:', 'tel:'].includes(parsed.protocol)) return;
const safeUrl = parsed.toString();
if (newTabLink) window.open(safeUrl, '_blank', 'noopener,noreferrer');
else window.location.assign(safeUrl);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/embed-dialog/index.tsx` around lines 266 - 269, In the
postMessage handler's CHAT_LINK_CLICK branch (the code that checks
e.data.type==='CHAT_LINK_CLICK' and uses newTabLink), remove the noisy
console.log(e), validate and sanitize e.data.url by constructing a URL and
allowing only http: or https: protocols (reject javascript:, data:, about:
etc.), and then open it safely: if newTabLink call window.open(sanitizedUrl,
'_blank', 'noopener,noreferrer') and otherwise set window.location.href =
sanitizedUrl; if URL parsing/validation fails, do nothing or log a harmless
warning via process logger instead.

Comment on lines +315 to +327
onClick={(event) => {
onClick?.(event);

const clickedUrl =
event.currentTarget.href ||
href ||
event.currentTarget.getAttribute('href') ||
'';

if (clickedUrl) {
emitChatLinkClick(clickedUrl);
}
}}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="web/src/components/floating-chat-widget-markdown.tsx"

# Show the relevant section with line numbers
nl -ba "$FILE" | sed -n '280,360p'

# Find where emitChatLinkClick is defined/used
rg -n "emitChatLinkClick" "$FILE"

# Check for any existing navigation-prevention patterns in the file
rg -n "preventDefault|stopPropagation" "$FILE"

# Check how anchors are rendered/processed (look for custom link component / preprocessing)
rg -n "anchor|link|remark|rehype|remarkGfm|remarkMath|onClick" "$FILE"

Repository: infiniflow/ragflow

Length of output: 105


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="web/src/components/floating-chat-widget-markdown.tsx"

# Basic sanity checks
test -f "$FILE"
echo "== file =="
wc -l "$FILE"
echo

echo "== excerpt around 295-345 (best-effort) =="
cat -n "$FILE" | sed -n '295,345p'
echo

echo "== emitChatLinkClick occurrences =="
rg -n "emitChatLinkClick" "$FILE" || true
echo

echo "== preventDefault/stopPropagation occurrences in file =="
rg -n "preventDefault|stopPropagation" "$FILE" || true
echo

echo "== onClick handler locations =="
rg -n "onClick=\{\(" "$FILE" || true
rg -n "onClick=\{\s*\(" "$FILE" || true
echo

Repository: infiniflow/ragflow

Length of output: 2657


Prevent default anchor navigation when emitting CHAT_LINK_CLICK (web/src/components/floating-chat-widget-markdown.tsx:315-327)

The custom a renderer calls emitChatLinkClick(clickedUrl) but never cancels the anchor’s default behavior, so clicks will still follow the link in addition to emitting the event.

Suggested fix
 onClick={(event) => {
   onClick?.(event);
+  if (event.defaultPrevented) return;
+  event.preventDefault();

   const clickedUrl =
     event.currentTarget.href ||
     href ||
     event.currentTarget.getAttribute('href') ||
     '';

   if (clickedUrl) {
     emitChatLinkClick(clickedUrl);
   }
 }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/floating-chat-widget-markdown.tsx` around lines 315 - 327,
The onClick handler for the custom anchor lets the browser navigate before/while
emitting CHAT_LINK_CLICK; update the handler in
floating-chat-widget-markdown.tsx (the onClick function that calls
onClick?.(event) and emitChatLinkClick(clickedUrl)) to call
event.preventDefault() when a clickedUrl is present (before emitChatLinkClick)
so the navigation is cancelled, then emit the event; keep calling the original
onClick?.(event) (either before or after preventDefault) to preserve existing
behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant