Skip to content

Commit ce8ca30

Browse files
fix(session-ui): render the link globe as its own element and drop it once the favicon loads (#61417)
1 parent 6d1c444 commit ce8ca30

3 files changed

Lines changed: 21 additions & 8 deletions

File tree

‎packages/session-ui/component-tests/markdown.spec.ts‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Page } from "@playwright/test"
1+
import type { Locator, Page } from "@playwright/test"
22
import { expect, sourceURL, story } from "../../stories/playwright/story"
33

44
const png = Buffer.from(
@@ -318,8 +318,9 @@ story("shows a stable GitHub mark without changing link text or other sites", as
318318
await expect(github).toHaveAttribute("href", "https://github.com/anomalyco/opencode/pull/540")
319319
await expect(github).toHaveText("#540")
320320
const mark = github.locator(".markdown-link-favicon[data-github]")
321-
expect(await mark.evaluate((icon) => getComputedStyle(icon, "::before").width)).toBe("14px")
322-
expect(await mark.evaluate((icon) => getComputedStyle(icon, "::before").maskImage)).toContain("data:image/svg+xml")
321+
const glyph = mark.locator(".markdown-link-favicon-glyph")
322+
expect(await glyph.evaluate((icon) => getComputedStyle(icon).width)).toBe("14px")
323+
expect(await glyph.evaluate((icon) => getComputedStyle(icon).maskImage)).toContain("data:image/svg+xml")
323324
await expect(mark.locator("img")).toHaveCount(0)
324325
await expect(markdown.getByRole("link", { name: "GitHub" }).locator(".markdown-link-favicon[data-github]")).toHaveCount(
325326
1,
@@ -360,15 +361,18 @@ story("keeps favicon space stable across loading and failure without fetching pr
360361
await expect(markdown).toHaveAttribute("data-markdown-ready", "")
361362
const docs = markdown.getByRole("link", { name: "docs" })
362363
const image = docs.locator(".markdown-link-favicon img")
364+
const globe = (link: Locator) => link.locator(".markdown-link-favicon-glyph")
363365
await expect.poll(() => requested.some((url) => url.includes("developer.mozilla.org"))).toBe(true)
364366
await expect(image).toHaveCSS("opacity", "0")
367+
await expect(globe(docs)).toBeVisible()
365368
expect((await docs.locator(".markdown-link-favicon").boundingBox())?.width).toBe(14)
366369
// A late web font swap also changes the link width; measure after fonts settle.
367370
await page.evaluate(() => document.fonts.ready.then(() => undefined))
368371
const width = await docs.evaluate((link) => link.getBoundingClientRect().width)
369372
release()
370373
await expect(image).toHaveAttribute("data-loaded", "")
371374
await expect(image).toHaveCSS("opacity", "1")
375+
await expect(globe(docs)).toHaveCount(0)
372376
await expect(image).not.toHaveAttribute("role", "button")
373377
expect(await image.evaluate((favicon) => favicon.onclick)).toBeNull()
374378
expect(await docs.evaluate((link) => link.getBoundingClientRect().width)).toBe(width)
@@ -384,6 +388,7 @@ story("keeps favicon space stable across loading and failure without fetching pr
384388
await expect(
385389
markdown.getByRole("link", { name: "missing" }).locator(".markdown-link-favicon img"),
386390
).not.toHaveAttribute("data-loaded", "")
391+
await expect(globe(markdown.getByRole("link", { name: "missing" }))).toBeVisible()
387392
await expect(markdown.getByRole("link", { name: "private" }).locator(".markdown-link-favicon img")).toHaveCount(0)
388393
await expect(markdown.getByRole("link", { name: "GitHub" }).locator(".markdown-link-favicon img")).toHaveCount(0)
389394
expect(requested).toHaveLength(2)

‎packages/session-ui/src/components/markdown.css‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,7 @@
153153
margin-inline-end: 4px;
154154
}
155155

156-
.markdown-link-favicon::before {
157-
content: "";
156+
.markdown-link-favicon-glyph {
158157
position: absolute;
159158
inset: 0;
160159
height: 14px;
@@ -163,7 +162,7 @@
163162
mask: url("./markdown-globe.svg") center / contain no-repeat;
164163
}
165164

166-
.markdown-link-favicon[data-github]::before {
165+
.markdown-link-favicon[data-github] .markdown-link-favicon-glyph {
167166
mask-image: url("./markdown-github.svg");
168167
}
169168

‎packages/session-ui/src/components/markdown.tsx‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,9 @@ function markExternalLinkFavicons(root: HTMLDivElement) {
361361
favicon.className = "markdown-link-favicon"
362362
favicon.setAttribute("aria-hidden", "true")
363363
const host = url.hostname.toLowerCase()
364+
const glyph = document.createElement("span")
365+
glyph.className = "markdown-link-favicon-glyph"
366+
favicon.appendChild(glyph)
364367

365368
if (host === "github.com") favicon.dataset.github = ""
366369
else if (
@@ -383,13 +386,19 @@ function markExternalLinkFavicons(root: HTMLDivElement) {
383386
})
384387
}
385388

389+
// The globe stands in only until the favicon loads; removing it keeps transparent favicons from showing it through.
390+
function showFavicon(image: HTMLImageElement) {
391+
image.dataset.loaded = ""
392+
image.parentElement?.querySelector(".markdown-link-favicon-glyph")?.remove()
393+
}
394+
386395
function setupExternalLinkFavicons(root: HTMLDivElement) {
387396
const loaded = (event: Event) => {
388397
const image = event.target
389398

390399
if (!(image instanceof HTMLImageElement) || !image.hasAttribute("data-markdown-favicon")) return
391400

392-
if (image.naturalWidth > 0) image.dataset.loaded = ""
401+
if (image.naturalWidth > 0) showFavicon(image)
393402
}
394403

395404
root.addEventListener("load", loaded, true)
@@ -901,7 +910,7 @@ export function Markdown(
901910
images?.update(container)
902911
previewImages(container)
903912
container.querySelectorAll<HTMLImageElement>("img[data-markdown-favicon]").forEach((image) => {
904-
if (image.complete && image.naturalWidth > 0) image.dataset.loaded = ""
913+
if (image.complete && image.naturalWidth > 0) showFavicon(image)
905914
})
906915

907916
// A button gets the labels of the run that creates it, so the others only need new ones when the language changes.

0 commit comments

Comments
 (0)