Back
Chapter 9

Running Code in the Website

View Source Code

Our extension is coming along pretty neatly I have to say but wouldn’t it be nice if we could have some visual feedback besides the icon which might or might not be visible based on whether the user pinned it or not

So I have a visual feedback that tells if a page already has a note or not

This is exactly what we’re going to build in this lesson and we will use the content scripts for that

What are content scripts

So head over to Chrome for developers documentation and the easiest thing is just to search for content scripts

So content scripts are files that run in the context of web pages

So that’s exactly what we’re going to do

We are going to inject our JavaScript into the web pages

They work in isolated worlds which means that they will not conflict with the underlying JavaScript that’s already running on the page

And we can inject them in static or dynamical or programmatical ways and we will look into the static one which means that we check whether the page matches a certain URL structure and if so we can inject custom JS or CSS files

In this example if the URL would match any subdomain of nytimes.com we would inject the my-style.css and the content-script.js

That doesn’t sound too complicated and if you remember in the second lesson we already saw that the WXT framework already created for us a simple content script which loads something when we reach to google.com

One thing to notice is that both content_scripts is an array matches is an array CSS and JS are also arrays

So you can set up multiple URL paths and set up multiple blocks of rows as well

Creating a content script in WXT

In WXT we create content scripts in the entrypoints folder

The file should be named content.ts or content/index.ts

// entrypoints/content/index.ts
export default defineContentScript({
  matches: ['<all_urls>'],
  main() {
    console.log('Content script running on:', window.location.href);
  }
});

This runs on all URLs

We can also be more specific

export default defineContentScript({
  matches: ['https://*/*'],
  main() {
    // Only runs on HTTPS pages
  }
});

Adding visual feedback

Let’s create a ribbon that shows when a page has notes

First let’s add the HTML

export default defineContentScript({
  matches: ['https://*/*'],
  main() {
    const ribbon = document.createElement('div');
    ribbon.id = 'tabnotes-ribbon';
    ribbon.textContent = 'Has Note';
    document.body.appendChild(ribbon);
  }
});

Now let’s style it

// entrypoints/content/index.ts
import './style.css';

export default defineContentScript({
  matches: ['https://*/*'],
  main() {
    const ribbon = document.createElement('div');
    ribbon.id = 'tabnotes-ribbon';
    ribbon.textContent = 'Has Note';
    document.body.appendChild(ribbon);
  }
});
/* entrypoints/content/style.css */
#tabnotes-ribbon {
  position: fixed;
  top: 0;
  right: 0;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 8px 25px;
  font-size: 11px;
  font-weight: 600;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.15);
  
  /* Position in corner */
  transform: rotate(45deg) translate(30%, -100%);
  transform-origin: bottom left;
  
  /* Ensure it's on top */
  z-index: 100000;
}

Perfect

Now we have a ribbon that shows on every page

But wait we only want it to show when the page actually has notes

The problem

Our current implementation shows the ribbon on every page but we only want it when notes exist

The ribbon should show when a page has notes hide when a page has no notes and update when notes are added or deleted

To do this we need to check storage from the content script

But content scripts can access storage directly

import { storage } from 'wxt/storage';
import getPageKey from '@/utils/getPageKey';

export default defineContentScript({
  matches: ['https://*/*'],
  async main() {
    const pageKey = getPageKey(window.location.href);
    const notesStorage = await storage.getItem('sync:notesStorage');
    const noteData = notesStorage?.[pageKey];
    
    if (noteData) {
      const ribbon = document.createElement('div');
      ribbon.id = 'tabnotes-ribbon';
      ribbon.textContent = 'Has Note';
      document.body.appendChild(ribbon);
    }
  }
});

Great

Now the ribbon only shows when a page has notes

But there’s still a problem

When you add or delete a note the ribbon doesn’t update automatically

We need messaging between the popup and content script to update the ribbon in real time

That’s what we’ll learn in the next chapter

Content script limitations

Content scripts run in isolated worlds

They can access the DOM but they can’t access variables from the page’s JavaScript

They can use some extension APIs like storage but not all of them

For example they can’t use chrome.tabs directly

They need to message the background script for that

What’s next

We’ve added visual feedback with a ribbon that shows when pages have notes

But we need to make it update dynamically when notes change

In the next chapter we’ll learn about messaging between different parts of the extension