Content scripts

Make page changes reversible

Insert one accessible note panel without corrupting page markup, duplicating UI, or leaving changes the user cannot undo.

A content script is a guest in someone else’s document. My rule for guests: touch as little as possible, label everything you touch, and leave the place as you found it.

For Page Notes that means one panel, added with DOM APIs, easy to remove.

Build, don’t rewrite

Never rewrite page HTML. Create your own elements and append them. Here’s the showPanel() function the message listener calls:

function showPanel(note) {
  document.getElementById('page-notes-panel')?.remove()

  const panel = document.createElement('aside')
  panel.id = 'page-notes-panel'

  const text = document.createElement('p')
  text.textContent = note

  const close = document.createElement('button')
  close.textContent = 'Close note'
  close.addEventListener('click', () => panel.remove())

  panel.append(text, close)
  document.body.append(panel)
}

Three things to notice. The first line removes an existing panel, so a second SHOW_PAGE_NOTE updates instead of duplicating. The note goes in with textContent, so a note that contains <b> shows the literal characters. And every ID and class carries the page-notes- prefix, so we don’t clash with the page.

Light DOM or shadow root

The page’s CSS applies to our panel. A site with p { color: white } on a white background makes our note invisible.

A shadow root gives your panel its own style scope. Page CSS doesn’t leak in, and yours doesn’t leak out. It’s the right choice when you ship a real UI. But it doesn’t hide anything from the page, and it still needs focus styles, accessible names, and readable colors.

For a small panel, prefixed classes and a few explicit styles are simpler. Use the shadow root when collisions actually bite you.

Track every side effect

Reversible means you can undo everything you did. Inserted nodes, event listeners, classes you added to page elements, inline styles, a scroll lock, the element that had focus. Write down each one as you add it, and give each one a matching undo.

Store the original value before you change it. And don’t assume the page kept it stable while your panel was open. Read the current value again before restoring, or restore only what you set.

Closing well

When the panel closes, return focus to something sensible: the element that had it before, or the top of the document. If the page deletes your panel from under you, the next SHOW_PAGE_NOTE must still work, which the ?.remove() handles.

Now test it. Show the panel three times with different text. Close it. Delete it from the page console. Show it again. Compare the DOM before and after: no duplicate IDs, no orphaned listeners, no leftover styles on page elements, and no note text ever interpreted as markup.

Lesson completed