Every HTTP and HTTPS page a user visits receives a silently injected copy of the Zotero translation framework. These content scripts run in an isolated JavaScript context inside the tab, giving them full read access to the live DOM while remaining sandboxed from the page’s own scripts. They are responsible for detecting which translators apply to the current page, runningDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/zotero/zotero-connectors/llms.txt
Use this file to discover all available pages before exploring further.
detectWeb() and doWeb() against the DOM when the user clicks the toolbar button, and communicating results back to the background process for saving.
How Content Scripts Are Loaded
The manifest’scontent_scripts block is populated at build time by gulpfile.js. For MV3 the entry looks like:
/*INJECT SCRIPTS*/ placeholder is replaced by the injectIncludeManifestV3 array (derived from injectInclude in gulpfile.js), so every listed file is executed in order at document_start — before the page’s own scripts run.
For MV2 (Firefox) the array is
injectIncludeBrowserExt, which omits inject/virtualOffscreenTranslate.js (not needed without a service-worker constraint) but is otherwise identical.The injectInclude Bundle
The following scripts are concatenated into the inject bundle in this order (from gulpfile.js):
messaging_inject.js and messagingGeneric.js are loaded before inject.js so that the Zotero.Messaging namespace is fully set up before Zotero.Inject.init() runs.
The shouldInject Guard
Before doing any real work the script checks several conditions to decide whether this frame should participate in translation at all (src/common/inject/inject.jsx):
shouldInject is false, Zotero.Inject.init() returns immediately.
Zotero.Inject.init()
The init() method is the entry point for the inject layer. It waits for the page to become visible (handling pre-render states), then:
Message Listeners Registered in _addMessageListeners()
| Message name | What it does in inject |
|---|---|
translate | Checks instanceID, then calls Zotero.PageSaving.onTranslate(...data) to run the selected translator |
saveAsWebpage | Calls Zotero.PageSaving.onSaveAsWebpage(data) to capture the page as a snapshot |
updateSession | Delegates to Zotero.PageSaving.onUpdateSession(data) |
pageModified | Debounced (1 000 ms) call to Zotero.PageSaving.onPageLoad(true) to re-run detection |
historyChanged | Same debounced re-detection — catches SPA navigation |
firstUse | Shows the first-run welcome prompt via Zotero.Inject.firstUsePrompt() |
expiredBetaBuild | Shows the build-expired prompt |
clipboardWrite | Calls navigator.clipboard.writeText(text) — clipboard API is unavailable in the background |
ping, confirm, and notify — are registered separately at the top of inject.jsx, before the Zotero.Inject object is defined, and only when isTopWindow is true:
| Message name | What it does in inject |
|---|---|
ping | Returns 'pong' so the background can verify scripts are injected (top window only) |
confirm | Shows a modal confirm dialog via Zotero.Inject.confirm(props) (top window only) |
notify | Injects and shows a Zotero.UI.Notification bar into the DOM (top window only) |
Translator Detection Flow
Page load
Zotero.PageSaving.onPageLoad() is called. It delegates to Zotero.Translate.Web which calls Zotero.Translators.getWebTranslatorsForLocation(url, rootURL).URL-match in background
getWebTranslatorsForLocation is a monkey-patched method (registered in MESSAGES). The call is transparently forwarded to the background process, which performs the fast regex-based URL match against the cached translator list and returns matching translators.detectWeb() in page
For each returned translator,
Zotero.Translate.Web runs detectWeb(doc, url) inside the SandboxManager — an eval-based sandbox that gives translator code access to the live DOM.Report back to background
Zotero.Connector_Browser.onTranslators(translators, instanceID, contentType) is called. Because Connector_Browser is also monkey-patched, this message is forwarded to the background, which updates _tabInfo[tab.id].translators and refreshes the toolbar icon via _updateExtensionUI(tab).pageSaving.js and sandboxManager.js
pageSaving.js
Implements
Zotero.PageSaving — the high-level orchestration of page-load detection, translation, and save-as-webpage flows. It manages the progress window and talks to Zotero.ItemSaver to dispatch translated items to the background.sandboxManager.js
Implements
Zotero.Translate.SandboxManager. Because content scripts cannot use eval() with external code in a straightforward way, the sandbox uses a carefully scoped eval wrapper that prepends sandbox properties into the evaluated code. This lets translator JavaScript access Zotero, doc, and url as if they were global.Google Docs Special Content Scripts
Google Docs pages receive an additional set of content scripts declared separately in the manifest:kixAddZoteroMenu.js— runs atdocument_start, hooks into the Kix editor (Google Docs internal name) to add the Zotero menu item to the Docs Add-ons menu.googleDocs.js+client.js— run atdocument_end, implement the Zotero ↔ Google Docs integration that allows inserting citations and bibliographies directly into a document via the Zotero desktop application.
