Content scripts

Run code in the page

Use a content script to access the current page DOM while keeping extension JavaScript isolated from page variables.

A content script is JavaScript that Chrome runs inside a web page on your behalf. It can read the page’s DOM and change it. That’s how Page Notes will show a note panel on top of the page.

The script runs in an isolated world, as we saw in the contexts lesson. Your variables and the page’s variables never meet. If the page has a global called note, and you have one too, nothing breaks.

Same DOM, different trust

Isolation is about JavaScript, not about the DOM. You and the page both read and write the same document. That has consequences in both directions.

Everything you read from the page is untrusted. A heading, a data attribute, a value in a hidden input: the page controls all of it. Everything you write to the page is visible to it. The page can read text you insert, remove nodes you added, copy your CSS class names, and fire DOM events at your elements.

So don’t stash extension state in attributes or hidden elements. Keep it in your script’s variables, or in extension storage.

What a content script can call

Content scripts get a small subset of the extension APIs: messaging and storage, mostly. They can’t call chrome.tabs or chrome.scripting.

When your content script needs privileged work done, it sends a small, specific message to the service worker or the popup, and lets them do it. Never build a bridge that exposes extension APIs to the page, and never inject credentials into it. Code you run in the page’s main world doesn’t get extension APIs either, so there’s no shortcut there.

Frames

A script in the top frame doesn’t see inside iframes. Each frame is its own document. You can ask Chrome to inject into all frames, but that widens what your extension touches. Page Notes only needs the top document, so we keep that explicit and never inject into frames.

A first content script

Let’s write content.js. It marks the document so we can tell later that we’ve been here, without overwriting a mark that’s already there:

if (!document.documentElement.dataset.pageNotes) {
  document.documentElement.dataset.pageNotes = chrome.runtime.getManifest().version
}

The data-page-notes attribute is namespaced with our extension name, so it won’t collide with anything the page uses.

Now open the page’s DevTools. In the Console, switch the context dropdown from the page to Page Notes and log document.documentElement.dataset. In the Sources panel, look under Content scripts to find your file.

Then do the hostile test. From the page context, delete the attribute. Your script must not break because the mark disappeared. DOM presence is a hint, never trusted state. We’ll inject this script on demand in the next lesson.

Lesson completed