# Emulated Chromium limits

> The one list of limits for the emulated Chromium browser extension.dev hosts, and how each line is kept true.

Canonical: https://docs.extension.dev/browsers/limits
This page as Markdown: https://docs.extension.dev/browsers/limits.md
Section: Browsers
Siblings: [Emulated Chromium](https://docs.extension.dev/browsers/emulated-chromium.md)
Map of every page: https://docs.extension.dev/llms.txt · Whole corpus: https://docs.extension.dev/llms-full.txt

---

# Emulated Chromium limits

Emulated Chromium runs an extension inside a web page, with no install and
no profile, and some things do not work there. Read this page when a call
behaves differently in the emulated browser than in Chrome, before you
change your code.

## Quick start

When a limit on this page blocks you, run the same project in a browser on
your machine:

```bash
extension dev --browser chrome
```

Everything on this page is a property of the emulated browser only. The
build is the same build.


## How it works

The surfaces an extension declares render and run, and an edit to any of
them arrives without a manual reload. The popup opens from the toolbar
button and takes clicks. The options page opens and keeps what it stores.
The side panel opens beside the page. A new tab page replaces the new tab
when the manifest declares one. The background service worker runs,
receives events and answers messages.

Storage and messaging work. `chrome.storage` reads and writes, and its data
survives a reload of the whole extension. Messages pass between the worker,
the surfaces and a content script.

The dev loop works. Four kinds of reload run here: the whole extension, the
service worker alone, the content scripts alone, and one page. The right one
is chosen from what you edited.

Most lines in the table below are readings, not opinions. The engine's
answer and real Chrome's answer to the same call are recorded, and every
difference is written into the engine's divergence ledger with a date to
read it again. Nothing in that ledger is scored, and no number on this page
stands for how close the two are. A few lines are read from the engine's own
tables rather than from a comparison: the namespaces that answer with
stand-in data, and the calls that refuse by name.

## Reference

| What does not work | What happens instead |
| --- | --- |
| A real address is not a real site | Only pages the emulated browser serves itself accept a content script: `example.com`, `www.example.com`, `example.org`, `example.net`, and six sample pages under `/fixtures/` on those hosts. A match for any other site does not run |
| No inspector for your extension | The emulated browser hosts no Elements, Console, Sources or Network panel. The drawer is the panel your own manifest declares. Your browser's own developer tools read the page the emulated browser runs in |
| Sign-in is always signed out | `chrome.identity.getAuthToken` fails and the profile reads empty. No OAuth flow completes |
| A new tab page cannot leave its frame | A new tab page that navigates moves only its own frame. The address bar and the tab do not follow, and a site that refuses to be framed shows that refusal inside the surface |
| Native messaging reaches no program | Only a host named `echo` or `com.extensiondev.preview.echo` answers, and it echoes the message back. Every other host fails with "Specified native messaging host not found." No program on your machine starts |
| Offscreen documents do not open | `offscreen.createDocument` is refused by name |
| A permitted host is still a cross-origin fetch | A fetch from the worker runs from the page and obeys CORS. A server that sends no `Access-Control-Allow-Origin` answers with a `TypeError` where Chrome reads the body |
| Machine information is refused | Eight calls under `chrome.system` refuse by name: CPU, memory, storage and display |
| Some namespaces answer with stand-in data | Most visited sites are a synthetic list. Printing acknowledges a job and prints nothing. Proxy and content settings are stored for read-back and enforce nothing. Registered user scripts are recorded and never injected. Web navigation events never fire. `chrome.debugger` attaches and detaches, and every `sendCommand` is refused |
| The browser's own pages are drawn | The extensions page is a stand-in that looks the part |
| One window | The emulated browser has one window. A tab cannot detach into another |
| Storage keyed by origin is shared | The worker runs on the engine page's origin, so IndexedDB and Cache Storage land in that origin's partition, beside every other extension opened there. `chrome.storage` is not affected |
| The sample page forgets | The page a content script runs on has the origin `null`, and its `localStorage` is a stand-in that resets with the document. A counter your script keeps reads 1 after a reload, where Chrome reads 2 |
| `navigator.locks` is denied | The guest frame refuses the Web Locks API |
| A rebuild reloads the page you are browsing | Chrome leaves that page alone with the old script until you reload it yourself |
| The popup survives a page edit | A page edit leaves an open popup open and reborn in place. Chrome closes every extension page on a reload. A manifest edit forces a full reload here too, and that closes the popup |
| The build rides inside the page | The whole build is loaded into the page as data, so a very large package can crash the renderer |
| With no behavior tables, nothing answers | The page fetches the table of engine behavior before it runs a call. If that document is missing or fails its check, every `chrome.*` call refuses by name instead of guessing |

## When it fails

- **A content script never runs**: the site is not one the emulated browser serves. Point the match at `example.com` or a `/fixtures/` page, or run the project in Chrome.
- **A fetch to a permitted host throws `TypeError`**: the server sends no `Access-Control-Allow-Origin`. The call is cross-origin here. Test that path in Chrome.
- **Every `chrome.*` call refuses by name**: the behavior table did not load or failed its check. Reload the page.
- **The renderer crashes on load**: the package is too large to ride inside the page. Run the project in Chrome.

## Next

- [Emulated Chromium](/browsers/emulated-chromium) for what the lane is and how to open it
- [Share an unpublished build for review](/share/unpublished-build-for-review) for the same engine behind a share link
