# Separate page and background responsibilities in a browser extension

Canonical page: https://yishto.com/en/learn/application-platforms/browser-extension-study-assistant/

Author: [Yishto](https://yishto.com/en/about/)

Design a user-triggered Chrome page-note assistant with separated contexts, narrow permissions, validated messages, and plain-text results that cannot trigger privileged actions.

Published: 2026-11-10
Updated: 2026-11-10

A page-note assistant reads selected text after a user clicks its action, previews it, and saves a note. An explicit action and preview define the data input. A sentence on the page saying “upload everything” remains text, not an instruction to the extension.

Start with one teaching domain and local storage. Summarization can later become a separate backend capability. The first version does not continually read every tab or place credentials in the DOM. This lesson targets Chrome Manifest V3; other browsers need compatibility checks.

## Separate execution contexts

A content script reads permitted page content within an isolation boundary from page scripts. Extension UI presents notes and confirmations, while the background service worker handles events and storage coordination. The worker has no page DOM and cannot directly read a selection.

Messages between these contexts need defined contracts. Data collected by a content script remains untrusted page data. Background handlers execute predefined actions, never commands inferred from text or arbitrary requests to read secrets and send them elsewhere.

Manifest V3 workers can stop. Globals and timers cannot serve as durable task records. Persist notes and necessary task state, then reload on wakeup. Opening a popup does not prove that earlier background objects still exist.

## Narrow permissions around user actions

The manifest declares metadata, scripts, and permissions. Identify the minimum APIs and page access needed after a click. Evaluate applicable capabilities such as `activeTab` and check each API's current requirements. Do not request every-site access for hypothetical future features.

Use specific hosts when ongoing access is actually required. Request optional access when the user needs another site, explaining what is read and where it is stored. Permission refusal can offer manual text entry instead of breaking the extension.

File URLs and incognito access need additional user permission. Browser internal pages also restrict injection. Show an unavailable-page explanation instead of repeated attempts. Injection permission does not remove the need to respect the chosen text boundary.

## Build and load the extension

Create root `manifest.json` with Manifest V3, action, and background declarations, plus popup, content script, and storage modules. Implement triggered reading, preview, and saving without networking. Executable logic belongs in the package rather than downloaded JavaScript.

Enable developer mode in Chrome's extension manager and load the unpacked directory. Select text on a teaching page and click the action. Return request ID, source URL, text, and capture time. Show the preview before the user confirms storage.

Validate action, field types, lengths, sender, tab, and source in background handlers. Navigation can make responses obsolete; the UI checks request identity. Storage and message errors need terminal states instead of permanent loading indicators.

## Parse addresses before validating them

Substring matching can accept attacker-controlled lookalike domains. The Python model parses scheme and hostname, permitting one synthetic HTTPS host while rejecting a suffix lookalike and HTTP.

```python
from urllib.parse import urlsplit
allowed_hosts = {"docs.example.test"}
def allowed(url):
    parsed = urlsplit(url)
    return parsed.scheme == "https" and parsed.hostname in allowed_hosts
assert allowed("https://docs.example.test/read")
assert not allowed("https://docs.example.test.evil.test/read")
assert not allowed("http://docs.example.test/read")
print(allowed("https://docs.example.test/read"))
```

The output is `True`. This is an address predicate, not Chrome host-permission enforcement or complete origin validation. It does not restrict ports. A real policy must decide port, frame, path, and sender requirements before accepting a message.

Address checks complement data validation. An allowed source can still send oversized text. A correct request ID cannot authorize access to another user's records. Reject unknown actions instead of falling back to generic execution.

## Treat model and page output as data

If summarization is added, show the exact selected payload and destination before upload. The backend verifies identity, size, and rate limits. Keep shared service secrets on the backend. Users can inspect extension packages and local storage, so those locations cannot protect a service secret that must remain confidential. Use narrowly scoped user tokens where authentication requires them, and keep them outside page DOM. Browser permissions do not authorize backend accounts.

Render model responses as plain text. Do not directly insert generated HTML. Links require separate handling, and tool actions come from controlled application contracts rather than commands extracted from summary prose. Hidden page prompts retain their status as source data.

Preserve source URL and captured text for review. Titles are not unique IDs, and saved fragments may no longer be locatable after page changes. Identify the capture instead of implying continued monitoring of the original page.

## Recover interruption and duplicate attempts

Reload persistent records after worker restart. Define whether a save continues when the popup closes and how its result appears next time. Reuse a stable operation ID when retrying a timed-out save to avoid duplicate notes.

Stop reading when permission is revoked. Cancel or reject obsolete capture results after navigation. Delete only the explicitly selected records. Preserve local payloads after upload failure instead of rereading a page that may have changed.


Distinguish top-level pages from embedded frames. Include actual frame context in captured data and validate the source where capture occurred. Explain inaccessible cross-origin frames instead of attributing all fragments to the top-level URL.

Persist ID, source, captured text, user edits, and schema version. A summary is derived data; deleting it need not delete the source. Editing the source makes an old summary stale. If synchronization is added, confirm by record and operation identity. Test concurrent tabs to prevent one result entering another preview, and retain capture provenance after tab closure.

## Exercise: verify boundaries

Implement preview, local saving, and source display. Test empty selections, oversized text, lookalike hosts, unknown actions, and script-like HTML. Render everything as text and reject privileged instructions embedded in content.

Stop the worker and trigger it again, confirming saved notes survive. Revoke permissions and check the fallback. Compare actual permissions with implemented features and test navigation. Python proves its URL cases; sender validation and lifecycle require extension execution tests.

## Sources & further reading

- [Extension architecture](https://developer.chrome.com/docs/extensions/get-started)
- [Declare extension permissions](https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions)
- [Extension message passing](https://developer.chrome.com/docs/extensions/develop/concepts/messaging)

Sources checked on 2026-11-10. Examples are teaching data, not production measurements.

## Related guides

- [Design security boundaries from assets and entry points](https://yishto.com/en/learn/delivery/security-boundaries-and-threat-models/)
- [The smallest RAG loop: retrieve, cite, answer](https://yishto.com/en/learn/rag/retrieval-pipeline/)
