On this page ▾
Build the extension
The Chrome extension lives in extension/. It detects Angular pages, creates the panel, and loads the devtools UI from extension/ui.
Files
extension/
manifest.json # Manifest V3, loopback host permissions, optional access to other hosts
background.js # Tracks which tabs run Angular
content-script.js # Relays the detection result to the background worker
detect-angular.js # Runs in the page, looks for ng-version or window.ng
devtools.html
devtools.js # Creates the panel on Angular pages
panel.html # The panel page and its status view
panel-bridge.js # Asks for host access, finds the dev server, connects the UI to it
icons/
ui/ # The built devtools UI (committed)
What the manifest asks for
| Key | Value |
|---|---|
permissions |
Empty. |
host_permissions |
*.localhost, 127.0.0.1 and [::1], over HTTP and HTTPS. *.localhost also matches localhost. |
optional_host_permissions |
http://*/* and https://*/*. The panel requests one host at a time, only when you click Allow access. |
content_scripts |
content-script.js and detect-angular.js, on every page. |
minimum_chrome_version |
111. |
Build
pnpm extension:build
This builds the devtools UI (pnpm devtools:build), then replaces extension/ui with a copy of dist/devtools-ui.
extension/ui is committed. If you change app/, run pnpm extension:build and commit the result. CI builds the extension and fails when extension/ui is stale.
Load in Chrome
chrome://extensions.
extension/ directory.
After a rebuild, click the reload icon on the extension card, then reopen DevTools.
Package for the Chrome Web Store
pnpm extension:zip
This runs extension:build, then writes dist/ng-devtools-extension.zip. The zip leaves out .DS_Store files.
Upload
- Bump
versioninextension/manifest.json. - Go to the Chrome Developer Dashboard.
- Click New item (or open the existing item) and upload the zip.
- Fill in the listing details and submit for review.
docs/privacy-policy.html.
How the panel connects
Host access
panel-bridge.js reads the origin of the inspected page. If the page is not served over http or https, it stops and says so.
It then calls chrome.permissions.contains() for <scheme>://<hostname>/* of the page. Loopback hosts pass, since the manifest grants them. For any other host it shows the Allow access button. The button calls chrome.permissions.request() for that one pattern and, if Chrome grants it, starts over.
Finding the server
With access granted, it looks for the devtools server under these paths, in order:
/__ng-devtools//__devframes/ng-devtools//__devframe//
Under each path it fetches __devframe/__connection.json, then __connection.json, with no credentials, no cache, no redirects and a 1.5 second timeout. The first response that is OK and parses as JSON wins.
If none answers, the status view lists every URL it tried and links to the setup section of the README.
Loading the UI
The panel loads ui/index.html with three query parameters:
| Parameter | Value |
|---|---|
baseURL |
The path that served the connection file, on the origin of the page. |
pageId |
The ng-devtools-page-id value the overlay keeps in sessionStorage, when it is set. |
theme |
The DevTools theme name, dark or default. |
Outside the extension, the UI accepts a baseURL only on its own origin. Inside the extension, it accepts any http or https URL. The panel only passes hosts the extension can reach.
On each navigation of the inspected page, the panel shows its status view again and repeats the whole search.
Theme
The panel follows the DevTools theme. panel-bridge.js reads chrome.devtools.panels.themeName at startup, sets data-theme on panel.html so the status view matches, and passes the name to the UI as the theme parameter (dark or default, which is light). Its setThemeChangeHandler updates panel.html and posts an ng-devtools:theme-change message to the UI frame when DevTools switches theme. ThemeService in the panel sets data-theme on <html>, and the CSS tokens in app/src/styles/_theme.scss follow it.
Elements panel selection
The overlay defines window.__ngDevtoolsComponentOf on the page. It takes an element and returns the id of the nearest component host, through shadow roots, or null.
When the Elements panel selection changes, panel-bridge.js evaluates it with $0. If it gets an id, it posts an ng-devtools:inspect-component message to the UI frame. The UI accepts the message only from its parent window and its own origin, and only while the Components tab is open. The tab then expands the parent rows, clears the filter if needed, selects the row and scrolls it into view.