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