Skip to content

Copy a shark:// link to a tab, and follow one back to it - #2950

Merged
pyricau merged 2 commits into
mainfrom
look-into-deeplinking-i-want-i
Aug 9, 2026
Merged

Copy a shark:// link to a tab, and follow one back to it#2950
pyricau merged 2 commits into
mainfrom
look-into-deeplinking-i-want-i

Conversation

@pyricau

@pyricau pyricau commented Aug 9, 2026

Copy link
Copy Markdown
Member

Right click a tab → Copy link to this tab → a shark:// URL. Click it anywhere — a chat message, an issue, a terminal — and Shark Explorer comes to the front with that place open in a new tab.

shark://vugs93jp/object?id=0x7f2a4b18
shark://vugs93jp/objects?query=Bitmap&exact=true
shark://vugs93jp/leaks?expanded=<group>
shark://vugs93jp/starred

Every Place has a spelling, and it carries the whole place rather than a shorthand: a filtered object list arrives filtered, a page of leaks arrives with the same groups unfolded. So any state the app can be in is reachable through a link — which is the point, since the next user of this is a tool or an agent printing "here is what I found" as a URL rather than as directions.

A link names a window, not a heap dump

The same dump open twice is what comparing two of them is, so a path would be ambiguous exactly when it matters. A window id is not, and it also settles what a link means once the window has gone: nothing. Following one then opens an empty window saying so, rather than the same object in whichever other window happened to have that file open.

A link always opens a tab of its own and never replaces what was being read.

How each OS delivers one

Told that shark:// is ours How the URL arrives
macOS CFBundleURLTypes in the packaged Info.plist Apple Event into the running process, via Desktop.setOpenURIHandler
Windows HKCU\Software\Classes\shark, written on first run A new process with the URL on its command line
Linux ~/.local/share/applications/shark-explorer.desktop, written on first run Same

For the two that start a process per link, DeepLinkPeers is how that process reaches the run that has the window: every run publishes a loopback port and a token under ~/.shark-explorer/runs, and a run holding a link asks each of the others in turn. Deliberately not single-instance — several explorers open at once, one per piece of work, is how this app is used, and making the second run hand its command line to the first and exit would take that away. Nobody is in charge and nothing breaks when any of them is killed.

The macOS handler goes through the same peers, because one installed app is handed every shark:// link on the machine, including ones naming a window of a run from source.

All of the OS end is best effort: every failure is one line in the log, never a refusal to start.

Tested

  • DeepLinkTest — 23 tests over the URL itself: every place round-trips, every synthetic node id (ROOT_OBJECT_ID, UNREACHABLE_NODE_ID, pile ids, Long.MAX_VALUE, negatives), URL-hostile query text, deterministic output, and each error message. Node ids are the exact unsigned 64 bits rather than hexObjectId, which masks to the low 32.
  • ExplorerWindowTest — 8 more: a link reaches only the window it names, a place is dropped once its tab opens, two links are two tabs in order, a link to a window that has gone opens one saying so and it lands beside the others.
  • ExplorerArgumentsTest, TabStripTest — a link on the command line is not a heap dump path; right clicking a tab copies the link to where that tab is; a link opens a tab of its own, in front.
  • End to end on macOS, from a createDistributable build: open "shark://<id>/leaks", the filtered object list, starred, and a link to a closed window, all read back out of the run's log file.

Windows and Linux could not be tried from here, so those two are best effort.

Only macOS could be verified, and here is why the other loop matters

shark/shark-explorer/AGENTS.md gains a section on it: a JVM launched from a shell script registers with LaunchServices as net.java.openjdk.java whatever its wrapper's plist says, so no link ever reaches a ./gradlew run or runNamed. Only a jpackage launcher gets its own bundle identity. Anything about links is a package, not a compile — the section has the loop, and says to read the result out of the log rather than off the screen, since following a link raises the app over whatever the person at the machine was doing.

pyricau and others added 2 commits August 9, 2026 17:14
Anything on screen can now be handed to someone else as one line of text
that puts them in front of it: right click a tab, "Copy link to this
tab", and clicking the URL brings the app forward and opens that place in
a new tab. Which is also how a script or an agent that has read a heap
dump can point at what it found, rather than describing the way there.

Every Place has a spelling, and it carries the whole place rather than a
shorthand for it — a filtered object list arrives filtered, a page of
leaks arrives with the same groups unfolded — so that any state the app
can be in is reachable through a link.

A link names a window, not a heap dump. The same dump open twice is what
comparing two of them is, so a path would be ambiguous exactly when it
matters, and a window id also settles what a link means once the window
is gone: nothing, which is an empty window saying so rather than the same
object in whichever other window had that file open.

macOS is told at build time, through CFBundleURLTypes, and hands the URL
to the running process. Windows and Linux have nothing like that in the
packaging, so the app registers itself on first run and the OS delivers a
link by starting a process with it — which then hands it to the run that
has the window, over a loopback port each run publishes. Deliberately not
single instance: several explorers open at once is how this app is used.

Only macOS could be tried here, so the other two are best effort. The
whole of the URL is unit tested either way, and the module's AGENTS.md
records the trap that cost the most: a JVM launched from a shell script
registers as net.java.openjdk.java whatever its wrapper says, so no link
ever reaches a run from Gradle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copying a link was on a tab and nowhere else, so a link to the object
under the pointer meant opening it in a tab first and right clicking
that. It is now the second item of every menu that offers "open in a new
tab" — a rectangle of the map, a row of the object list or of the leaks,
a step of a chain, a field of the details panel, a starred object — and
the only item on the two things that name a place without a choice of
where to open it: a tab, and a button of the screen bar.

Every one of them goes through one `copyLink` in `HeapDumpExplorer`, for
the same reason opening goes through one `open`: five of them would
drift. And the menu item is called "Copy link" everywhere rather than
naming its surface, so that the same item doesn't read as five.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@pyricau
pyricau merged commit ca8c455 into main Aug 9, 2026
16 checks passed
@pyricau
pyricau deleted the look-into-deeplinking-i-want-i branch August 9, 2026 15:53
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.

1 participant