A content script runs near a web page and can inspect or change its DOM, but its code has a different trust and lifecycle boundary from the page’s own scripts. The page can alter labels, insert deceptive nodes, and reorder elements between reads. An isolated execution world reduces direct JavaScript variable collisions; it does not make page text trustworthy. The script must identify the exact origin and page structure it supports, read only the fields needed for its feature, and place extension UI without taking over the host page’s controls. A page update or single-page navigation can invalidate every selector.
Content Script Isolation and DOM Mutation
Working case
The case helper highlights overdue inspections on a permit page. A document update renames the visible date label and adds a hidden duplicate input. A broad text query now reads the hidden value and marks the wrong case overdue. Another site has a similar CSS class name, but it is not the permit portal. The extension confines injection to the portal origin, locates the case record through a stable attribute and nearby semantic label, validates the case number and date, then displays its own compact panel. If structure validation fails, it shows an unavailable state and directs the reviewer to the original page instead of guessing.
Implementation boundary
function validCaseCard(card) {
return /^CASE-[0-9]{2,6}$/.test(card.caseId) &&
card.matches === 1 && Number.isFinite(Date.parse(card.dueDate));
}
console.log(validCaseCard({ caseId: 'CASE-47', matches: 2, dueDate: '2026-10-04' }));
// Output: falseTreat selectors as an adapter to one page contract, not a universal parser. Validate extracted fields before they enter extension storage or a message. Read text with safe DOM APIs and write extension text through text nodes; do not interpret page-provided markup as extension UI. Namespace injected nodes and make insertion idempotent so route changes or repeated execution do not create duplicate panels. Observe only the container needed for changes, debounce expensive re-reads, and disconnect observers when the tab or route leaves scope. Do not modify the host form’s values or submit on the user’s behalf without a separate explicit action. Keep focus order, labels, and keyboard access intact. Test the real page’s responsive and localization variants; translated labels can break brittle text selectors.
Cost and boundaries
A full document scan is O(N) for N DOM nodes, and a mutation observer that reparses the page on every change can become O(N × M) across M mutations. Scope queries to a stable container, cap observed work, and batch changes. Injected panels add DOM and style cost to a page the extension does not own. A tiny adapter can fail after a site release, so monitor extraction failures by adapter version without retaining private page text. Measure time from user action to panel, observer callback count, duplicate insertion rate, and failure by supported page version. The fallback is safer than silently using a stale selector.
Failure trace
Insert a hidden duplicate case ID and require the adapter to reject ambiguity. Change the locale, date format, and case card order while the extension is open. Repeat injection three times and confirm one panel remains. Trigger 83 rapid DOM mutations and inspect observer work; it must stay bounded. Add markup that looks like extension instructions and verify it is treated as text. Remove the target container and show a visible unavailable state without altering the host form. Use keyboard only to reach the panel, return to page content, and submit the original form. Move within the site without a full reload and verify observers and old data are cleaned up.
Verification
- Ambiguous and hidden duplicates cannot decide a case.
- Repeated injection creates one panel.
- Page strings reach text sinks only.
Practice drill
Implement one adapter for a case 47 page at revision 29. Extract only the case ID, due date, and status. Require an unambiguous container and typed values. Inject a panel with a stable marker, then simulate two route changes and 83 mutation callbacks. Record the number of reads, panels, and active observers. Replace the case ID node with attacker-written text and prove the helper neither renders HTML nor sends it as an instruction.
Decision note
The host page is a data source the extension does not control; selector failure must stop the derived action.
Common Mistakes
- Assuming isolation makes page text trusted.
- Scanning the whole DOM after every mutation.
- Changing a host form without a separate user action.
Related lessons
Browser Extension Trust and Lifecycle; Extension Host Permissions and User Invocation; Extension Background Events and Durable State; Extension Message Contracts and Page Data Boundary; DOM Sinks and Trusted Content; Browser Execution and Resource Lifecycle.
Connected practice
Build Project: permit checklist browser helper and review Web Development: extension boundary decisions quiz.
