Building a Firefox Browser Extension - Part Three
As is often the case, I reached an inflection point while tackling this project: I went from trying to simply understand the structure of Firefox extensions to having concrete designs and ideas in place. In other words, I very quickly went from an empty file to a small, working extension.
Things really started getting easier when I figured out the relationships between the components of a browser and their significance in extension design. I created a sequence diagram to show how a user interacts with my extension and to illustrate how everything works together.

The Popup
Every component (popup, active tab) is sort of a miniature webpage with its own HTML, CSS, and JavaScript. The popup is a short-lived webpage that exists for as long as it is open. It is generally the first point of contact for a user, although there are other interfaces such as the context menu, which comes up when you left-click a webpage.
My popup contains an event listener that listens for when a user selects the button find rotten links. Then, the popup hands control over to the content script.
browser.executeScript({
file: “/content_scripts/rotten.js”
});
The Content Script
This handy little thing is the JavaScript that will execute in the active tab. In my extension, it’s primary task is getting all the links in the page and organizing them into two arrays, one for internal links internalLinks and one for external externalLinks. The links are gathered and packaged in JSON objects.
{
href: link.href,
text: link.innerText,
statusCode: “”
}
Objects for External links don’t include the statusCode field: they won’t be fetched like internal links, but more on that in the conclusion. Once all the links have been collected into the arrays, they are transmitted to the background script via the runtime API.
browser.runtime.sendMessage({
type: “internal”,
content: internalLinks
});
browser.runtime.sendMessage({
type: “external”,
content: externalLinks
});
The Background Script
This one runs globally in the browser. It has access (depending on what permissions you give it in manifest.json) to every component, as well as other tabs.
It listens for and receives the message sent from the content script.
browser.runtime.onMessage.addListener((message) => {
// Lots of steps
});
Inside the listener function, the message.type is read to determine if the message received contains internal or external links. If internal, then while iterating through the array, fetch is called on each link and the resulting status code is stored in the statusCode field of the JSON object. For any link that responds with a status other than 200, find is called to highlight it in the body of the webpage. I only got the find API to work when called from the background script.
Finally, all the links, in the original message object with their new status codes, are transmitted to the popup.
browser.runtime.sendMessage({
type: “report-internal”,
content: message.content
});
browser.runtime.sendMessage({
type: “report-external”,
content: message.content
});
The Popup Again
The message containing the internal and external links are received and displayed in the popup with the dead links highlighted. Since the external links were not fetched, the user can here examine them manually to make sure they are connected properly. This treatment of external links was not my original design, as I discuss below.
Conclusion and Complications
I ran into two significant hiccups. First, when transmitting the internal link information from background to popup script, the status codes are not updated and remain empty strings. It is clearly an async/await issue, wherein the rest of the script is executing while the separate fetch process carries on more slowly. Therefore, the message gets sent before the fetch call returns a response. I haven't yet found an elegant solution.
Second, I can't get fetch to work on the external links since it is prohibited by the same-origin security policy, a security measure meant to guard against things like cross-site scripting (XSS) attacks. As I read about this problem, it seems there is a way to circumvent this policy, namely with CORS (Cross-origin Resource Sharing), but I haven't figured out how to implement it yet. These are the challenges that await!





