Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,13 @@ describe('SnapshotFrame', () => {

expect(onPickNode).not.toHaveBeenCalled()
})

it.each([true, false])('never lets a clicked link navigate the frame to the live site (pick mode %s)', (pickMode) => {
const { container } = render(<SnapshotFrame html={BODY} pickMode={pickMode} showHidden={false} onHoverNode={() => undefined} onPickNode={() => undefined} />)
const link = loadedLink(container)

const notCancelled = fireEvent.click(link)

expect(notCancelled).toBe(false)
})
})
10 changes: 10 additions & 0 deletions apps/studio-ui/src/content-pane/snapshot-frame.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ export function SnapshotFrame ({ html, pickMode, showHidden, hoveredSelector, on
const handleMouseOver = (event: MouseEvent): void => { propsRef.current.onHoverNode(nodeIdOf(event.target)) }
const handleMouseOut = (): void => { propsRef.current.onHoverNode(undefined) }
const handleClick = (event: MouseEvent): void => {
// The snapshot is a picture of the page, not the page: a link must never navigate this frame to the live
// site (which usually refuses to be framed), whether or not a pick is under way.
if (linkOf(event.target) !== undefined) event.preventDefault()
if (!propsRef.current.pickMode) return
const nodeId = nodeIdOf(event.target)
if (nodeId === undefined) return
Expand Down Expand Up @@ -97,6 +100,13 @@ export function SnapshotFrame ({ html, pickMode, showHidden, hoveredSelector, on
)
}

/** The link at or above `target`, if any (same cross-realm caveat as `nodeIdOf`). */
function linkOf (target: EventTarget | null): Element | undefined {
const element = (target as Node | null)?.nodeType === Node.ELEMENT_NODE ? (target as Element).closest('a[href], area[href]') : null

return element ?? undefined
}

/** The `data-oc-node` id of the stamped element at or above `target`. The target belongs to the iframe's own realm, so `target instanceof Element` is false for it: test the node type instead (issue #153). */
function nodeIdOf (target: EventTarget | null): string | undefined {
const element = (target as Node | null)?.nodeType === Node.ELEMENT_NODE ? (target as Element).closest(`[${CSS.escape(NODE_ID_ATTRIBUTE)}]`) : null
Expand Down
2 changes: 1 addition & 1 deletion apps/vscode-extension/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opencraw/vscode",
"version": "0.1.1",
"version": "0.1.2",
"private": true,
"description": "OpenCraw in the editor: starts the Studio server and opens a folder, recipe or document in the browser.",
"type": "module",
Expand Down
Loading