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:
extension dev --browser chromeEverything 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.comor a/fixtures/page, or run the project in Chrome. - A fetch to a permitted host throws
TypeError: the server sends noAccess-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 for what the lane is and how to open it
- Share an unpublished build for review for the same engine behind a share link