mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: open coder_app links in new tab when open_in is tab (#23000)
Fixes #18573 ## Changes When a `coder_app` resource sets `open_in = "tab"`, clicking the app link now opens in a new browser tab instead of navigating in the same tab. `target="_blank"` and `rel="noreferrer"` are set inline on the `<a>` elements in `AppLink.tsx`, gated on `app.open_in === "tab"`. This follows the codebase convention of co-locating `target` and `rel` at the render site. `noreferrer` suppresses the Referer header to avoid leaking workspace IDs to destination servers and implies `noopener`. `noopener` prevents tabnabbing — without it, the opened page can redirect the Coder dashboard tab via `window.opener`. This is especially relevant for same-origin path-based apps, which would otherwise have full DOM access to the dashboard. > **Future enhancement**: template admins could opt into sending the referrer via a `coder_app` setting, enabling feedback pages built around workspace context. ## Tests A vitest case is added in `AppLink.test.tsx` (rather than a Storybook story, since the assertions are purely behavioral with no visual component): - **`sets target=_blank and rel=noopener noreferrer when open_in is tab`** — renders the app link with `open_in: "tab"` and asserts `target="_blank"` and `rel="noreferrer"` are present on the anchor. ## Slim-window behavior The `slim-window` test case and the `openAppInNewWindow()` comment in `apps.ts` have been split out into a follow-up PR for separate review, since the `window.open()` / `noopener` tradeoffs there deserve dedicated discussion. --------- Co-authored-by: Kayla はな <kayla@tree.camp>
This commit is contained in:
co-authored by
Kayla はな
parent
91ec0f1484
commit
d3c9469e13
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
MockWorkspace,
|
||||
MockWorkspaceAgent,
|
||||
MockWorkspaceApp,
|
||||
} from "testHelpers/entities";
|
||||
import { renderWithAuth } from "testHelpers/renderHelpers";
|
||||
import { screen } from "@testing-library/react";
|
||||
import { AppLink } from "./AppLink";
|
||||
|
||||
const renderAppLink = (app: typeof MockWorkspaceApp) => {
|
||||
return renderWithAuth(
|
||||
<AppLink app={app} workspace={MockWorkspace} agent={MockWorkspaceAgent} />,
|
||||
);
|
||||
};
|
||||
|
||||
// Regression test for https://github.com/coder/coder/issues/18573:
|
||||
// open_in="tab" was not opening links in a new tab.
|
||||
describe("AppLink", () => {
|
||||
it("sets target=_blank and rel=noreferrer when open_in is tab", async () => {
|
||||
renderAppLink({ ...MockWorkspaceApp, open_in: "tab" });
|
||||
const link = await screen.findByRole("link");
|
||||
expect(link).toHaveAttribute("target", "_blank");
|
||||
expect(link).toHaveAttribute("rel", "noreferrer");
|
||||
});
|
||||
});
|
||||
@@ -135,7 +135,12 @@ export const AppLink: FC<AppLinkProps> = ({
|
||||
|
||||
const button = grouped ? (
|
||||
<DropdownMenuItem asChild>
|
||||
<a href={canClick ? link.href : undefined} onClick={link.onClick}>
|
||||
<a
|
||||
href={canClick ? link.href : undefined}
|
||||
onClick={link.onClick}
|
||||
target={app.open_in === "tab" ? "_blank" : undefined}
|
||||
rel={app.open_in === "tab" ? "noreferrer" : undefined}
|
||||
>
|
||||
{icon}
|
||||
{link.label}
|
||||
{ShareIcon && <ShareIcon />}
|
||||
@@ -143,7 +148,12 @@ export const AppLink: FC<AppLinkProps> = ({
|
||||
</DropdownMenuItem>
|
||||
) : (
|
||||
<AgentButton asChild>
|
||||
<a href={canClick ? link.href : undefined} onClick={link.onClick}>
|
||||
<a
|
||||
href={canClick ? link.href : undefined}
|
||||
onClick={link.onClick}
|
||||
target={app.open_in === "tab" ? "_blank" : undefined}
|
||||
rel={app.open_in === "tab" ? "noreferrer" : undefined}
|
||||
>
|
||||
{icon}
|
||||
{link.label}
|
||||
{ShareIcon && <ShareIcon />}
|
||||
|
||||
Reference in New Issue
Block a user