1# Deep linking to the Perfetto UI 2 3This document describes how to open traces hosted on external servers with the 4Perfetto UI. This can help integrating the Perfetto UI with custom dashboards 5and implement _'Open with Perfetto UI'_-like features. 6 7## Using window.open and postMessage 8 9The supported way of doing this is to _inject_ the trace as an ArrayBuffer 10via `window.open('https://ui.perfetto.dev')` and `postMessage()`. 11In order to do this you need some minimal JavaScript code running on some 12hosting infrastructure you control which can access the trace file. In most 13cases this is some dashboard which you want to deep-link to the Perfetto UI. 14 15#### Open ui.perfetto.dev via window.open 16 17The source dashboard, the one that knows how to locate a trace and deal with 18ACL checking / oauth authentication and the like, creates a new tab by doing 19 20```js 21var handle = window.open('https://ui.perfetto.dev'); 22``` 23 24The window handle allows bidirectional communication using `postMessage()` 25between the source dashboard and the Perfetto UI. 26 27#### Wait for the UI to be ready via PING/PONG 28 29Wait for the UI to be ready. The `window.open()` message channel is not 30buffered. If you send a message before the opened page has registered an 31`onmessage` listener the messagge will be dropped on the floor. 32In order to avoid this race, you can use a very basic PING/PONG protocol: keep 33sending a 'PING' message until the opened window replies with a 'PONG'. 34When this happens, that is the signal that the Perfetto UI is ready to open 35traces. 36 37#### Post a message the following JavaScript object 38 39```js 40 { 41 'perfetto': { 42 buffer: ArrayBuffer; 43 title: string; 44 fileName?: string; // Optional 45 url?: string; // Optional 46 } 47 } 48``` 49 50`buffer` is the ArrayBuffer with the actual trace file content. This is 51typically something that you obtain by doing a `fetch()` on your backend 52storage. 53 54`title` is the human friendly trace title that will be shown in the 55sidebar. This can help people to disambiguate traces from several tabs. 56 57`fileName` will be used if the user clicks on "Download". A generic name will 58be used if omitted. 59 60`url` is used if the user clicks on the "Share" link in the sidebar. This should 61print to a URL owned by you that would cause your dashboard to re-open the 62current trace, by re-kicking-off the window.open() process herein described. 63If omitted traces won't be shareable. 64 65### Code samples 66 67See [this example caller](https://bl.ocks.org/chromy/170c11ce30d9084957d7f3aa065e89f8), 68for which the code is in 69[this GitHub gist](https://gist.github.com/chromy/170c11ce30d9084957d7f3aa065e89f8). 70 71Googlers: take a look at the 72[existing examples in the internal codesearch](http://go/perfetto-ui-deeplink-cs) 73 74### Common pitfalls 75 76Many browsers sometimes block window.open() requests prompting the user to allow 77popups for the site. This usually happens if: 78 79- The window.open() is NOT initiated by a user gesture. 80- Too much time is passed from the user gesture to the window.open() 81 82If the trace file is big enough, the fetch() might take long time and pass the 83user gesture threshold. This can be detected by observing that the window.open() 84returned `null`. When this happens the best option is to show another clickable 85element and bind the fetched trace ArrayBuffer to the new onclick handler, like 86the code in the example above does. 87 88Some browser can have a variable time threshold for the user gesture timeout 89which depends on the website engagement score (how much the user has visited 90the page that does the window.open() before). It's quite common when testing 91this code to see a popup blocker the first time the new feature is used and 92then not see it again. 93 94### Where does the posted trace go? 95 96The Perfetto UI is client-only and doesn't require any server-side interaction. 97Traces pushed via postMessage() are kept only in the browser memory/cache and 98are not sent to any server. 99 100## Why can't I just pass a URL? 101 102_"Why you don't let me just pass a URL to the Perfetto UI (e.g. ui.perfetto.dev?url=...) and you deal with all this?"_ 103 104The answer to this is manifold and boils down to security. 105 106#### Cross origin requests blocking 107 108If ui.perfetto.dev had to do a `fetch('https://yourwebsite.com/trace')` that 109would be a cross-origin request. Browsers disallow by default cross-origin 110fetch requests. 111In order for this to work, the web server that hosts yourwebsite.com would have 112to expose a custom HTTP response header 113 (`Access-Control-Allow-Origin: https://ui.perfetto.dev`) to allow the fetch. 114In most cases customizing the HTTP response headers is outside of dashboard's 115owners control. 116 117You can learn more about CORS at 118https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS 119 120#### Content Security Policy 121 122Perfetto UI uses a strict Content Security Policy which disallows foreign 123fetches and subresources, as a security mitigation about common attacks. 124Even assuming that CORS headers are properly set and your trace files are 125publicly accessible, fetching the trace from the Perfetto UI would require 126allow-listing your origin in our CSP policy. This is not scalable. 127 128You can learn more about CSP at 129https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP 130 131#### Dealing with OAuth2 or other authentication mechanisms 132 133Even ignoring CORS, the Perfetto UI would have to deal with OAuth2 or other 134authentication mechanisms to fetch the trace file. Even if all the dashboards 135out there used OAuth2, that would still mean that Perfetto UI would have to know 136about all the possible OAuth2 scopes, one for each dashboard. This is not 137scalable. 138 139## Source links 140 141The source code that deals with the postMessage() in the Perfetto UI is 142[`post_message_handler.ts`](/ui/src/frontend/post_message_handler.ts) 143