CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/manifest-v3-test-surface-reference

Pure-reference catalog of the Manifest V3 test surface for Firefox + Chromium browser extensions. Maps each manifest field that changed from MV2 (manifest_version, background.service_worker vs background.scripts, action vs browser_action / page_action, host_permissions split, web_accessible_resources object-form, content_security_policy object-form), the runtime restrictions service workers impose (no DOM, no XMLHttpRequest, no localStorage, ephemeral lifecycle, synchronous listener registration, alarms instead of setTimeout), and the Firefox-vs-Chrome key matrix (browser_specific_settings.gecko, externally_connectable / offline_enabled gaps, MV2-only user_scripts manifest key). Use as the manifest-surface reference when authoring extension tests across both browsers.

70

Quality

88%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

manifest-matrix.mdreferences/

Manifest matrix + field shapes - manifest-v3-test-surface-reference

Deep-lookup detail for the summary tables in SKILL.md: exact MV2/MV3 field shapes, the full Firefox/Chrome key matrix, and the two service-worker constructs (offscreen documents, keep-alive). Sources are consolidated in the link list at the bottom.

Exact field shapes

background

MV2:

{
  "background": {
    "scripts": ["backgroundContextMenus.js", "backgroundOauth.js"],
    "persistent": false
  }
}

MV3:

{
  "background": {
    "service_worker": "service_worker.js",
    "type": "module"
  }
}

The MV3 service_worker field is a single string (not an array); type is optional and only valid as "module". The MV2 persistent flag is removed entirely.

host_permissions split

"Host permissions in Manifest V3 are a separate field; you don't specify them in "permissions" or in "optional_permissions"."

MV2:

"permissions": ["tabs", "bookmarks", "https://www.blogger.com/"],
"optional_permissions": ["unlimitedStorage", "*://*/*"]

MV3:

"permissions": ["tabs", "bookmarks"],
"optional_permissions": ["unlimitedStorage"],
"host_permissions": ["https://www.blogger.com/"],
"optional_host_permissions": ["*://*/*"]

"content_scripts[].matches" is unchanged between MV2 and MV3.

web_accessible_resources shape change

MV2 (flat string array):

"web_accessible_resources": [
  "images/*",
  "style/extension.css",
  "script/extension.js"
]

MV3 (array of objects, each scoping resources to URL patterns or extension IDs):

"web_accessible_resources": [
  { "resources": ["images/*"], "matches": ["*://*/*"] },
  {
    "resources": ["style/extension.css", "script/extension.js"],
    "matches": ["https://example.com/*"]
  }
]

Test implication: a page-context fetch(chrome.runtime.getURL('...')) that worked under MV2 may 404 under MV3 if the requester's origin isn't covered by a matches pattern.

Firefox vs Chrome manifest key matrix

Every documented manifest key with Firefox / Chrome availability and MV2 / MV3 status per mdn-manifest. Tests must gate on browser detection or split into per-browser fixtures when a key is "not supported" in one column.

KeyMV2MV3FirefoxChromeTest note
manifest_versionyesyesyesyesMandatory; first assertion
nameyesyesyesyesMandatory
versionyesyesyesyesMandatory; semver in Firefox AMO
actionnoyesyesyesUnified popup slot
browser_actionyesnoyes (MV2 only)yes (MV2 only)Renames to action in MV3
page_actionyesnoyes (MV2; different in MV3)yes (MV2 only)Firefox keeps a different page-action shape
backgroundyesyesyesyesShape changes per above
browser_specific_settingsyesyesyesnoTests asserting extension ID stability rely on this in Firefox
content_scriptsyesyesyesyesSame shape
content_security_policyyesyesyesyesShape changes (object in MV3)
declarative_net_requestyesyesyesyesReplaces webRequest-blocking surface
externally_connectableyesyesnoyesCross-origin runtime messaging - Chrome only
host_permissionsnoyesyesyesNew in MV3; permission-prompt test surface
offline_enabledyesyesnoyesChrome-only manifest key
optional_host_permissionsnoyesyesyesRuntime host grants
optional_permissionsyesyesyesyesRuntime API grants
permissionsyesyesyesyesAPI permissions only in MV3
protocol_handlersyesyesyes (Firefox only)noFirefox-only register-protocol surface
sidebar_actionyesyesyes (Firefox/Opera)noSidebar UI - not in Chrome
storage (as manifest key)yesyesnoyesNote: the storage API works in both
theme_experimentyesyesyes (Firefox only)noExperimental theming
user_scripts (manifest key)yesnoyes (MV2 only)yes (MV2 only)MV3 replaces with userScripts API
web_accessible_resourcesyesyesyesyesShape changes (object array in MV3)

browser_specific_settings.gecko

Firefox-specific metadata (extension ID, min Firefox version) lives under browser_specific_settings.gecko. Chrome silently ignores this key:

{
  "browser_specific_settings": {
    "gecko": {
      "id": "@addon-example",
      "strict_min_version": "42.0"
    }
  }
}

Test note: AMO submission validation requires a stable gecko.id for signing - a test asserting the built zip carries a deterministic ID prevents accidental ID drift across builds.

Service-worker constructs

Offscreen documents

DOM-requiring work in MV3 goes to an offscreen document:

chrome.offscreen.createDocument({
  url: chrome.runtime.getURL('offscreen.html'),
  reasons: ['CLIPBOARD'],
  justification: 'testing the offscreen API',
});

Offscreen documents communicate with the service worker via runtime.sendMessage / runtime.onMessage only - they don't share other extension APIs.

Keep-alive heartbeat

The keep-alive pattern (calling chrome.runtime.getPlatformInfo on a ~25s interval to reset the idle timer, or writing to chrome.storage.local every 20s) is documented but Chrome explicitly limits its use to enterprise/education managed extensions, "reserves the right to take action" against others, and notes a waitUntil()-style API is under discussion in the W3C WebExtensions Community Group (WECG).

SKILL.md

tile.json