Building a Firefox Browser Extension - Part Four
My extension is complete! I submitted it to Mozilla Add-ons in mid-July and it was approved a few days later (addons.mozilla.org). Also, the source code can be found on Codeberg I'll briefly describe the finishing touches I made.
From Popup to Sidebar
My initial design used a popup for interacting with the extension. The user would click the extension icon in the browser toolbar to open the popup, click the Find Rotten Links button, and view the results in the popup. Once I was finished with this design, it immediately became clear I needed a different approach: the popup is not designed to persist, so clicking out of it destroys the latest content within it. Worse, it was easy to accidentally close the popup by switching tabs or simply clicking in the active tab.
The sidebar was the solution. It persists until it is deliberately closed by pressing the X button, and it displays the results much more clearly than the popup. Now, the user clicks the extension icon in the browser toolbar and the sidebar opens.
Solving the Cross-Origin Request Problem
This problem was different than I expected. Initially, I thought that the calls to fetch() on the external links weren't working because of how I had written the requests. For example, my original call to fetch() looked like this:
let myReq = new Request(link.href);
fetch(myReq).then((response) => {
if (response.status != 200) {
browser.runtime.sendMessage({
type: "internal",
content: link.innerText
});
}
internalLinks.push({
href: link.href,
text: link.innerText,
status_code: ""
});
This example shows the handling of internal links, but I used the same approach for the external links: it simply was never pushed to the remote tree since it didn't work. The fetch() calls for the external links kept returning something like this:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at <url>. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 302.
or
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at <url>. (Reason: CORS request did not succeed). Status code: (null).
All the external links violated the Same Origin Policy, so none of them returned status codes. I couldn't sort out why. I decided to remove the fetch() calls for external links and look into it another time.
Later, when I had replaced the popup with a sidebar, I decided to try handling external links again. I thought the problem was my fetch requests failing to satisfy CORS (Cross Origin Resource Sharing), which specifies the criteria for simple requests. Simple requests only use methods like GET, HEAD, POST, and they are limited in other ways as well. The overarching purpose of CORS is to prevent things like XSS attacks (Cross Site Scripting), where malicious code originating outside the webpage is shunted in and executed, potentially stealing PII, modifying sensitive information, etc. A simple request is deemed safe by CORS since, among other safeguards, it won't pull code from another origin.
In an attempt to make my request compliant with CORS, I explicitly indicated the method I wanted to use, HEAD (note: this is not actually the solution as I'll explain below).
let request = new Request(link.href, {
method: "HEAD"
});
When I ran the code again, it worked! All of the external links successfully returned status codes. Some would return codes like 403 FORBIDDEN, which is itself likely a consequence of CORS: though my request is compliant, the server from the separate origin can choose to reject even simple requests by omitting the Access-Control-Allow-Origin parameter. This is beyond my control, and further, it doesn't pose a problem for my extension, which only needs to verify that a link actually points to an existing resource by receiving a status code other than 404. I was pretty happy that the problem was solved.
Sitting down to write this entry, I wanted to try to explain why the changes I made solved the problem. I went to the GitHub repository and pulled down old commits to find the moment when fetching external links began working. Turns out, my requests were simple requests all along. The real problem was the restrictions imposed by Popups. What I gather is that Popups have a more strict Content Security Policy than Sidebars: it was the functionality of the Popup itself that was triggering the Cross-Origin Request Blocked console message. When I changed the design to a Sidebar, the problem was solved.
Publishing
This final step was fairly straightforward. Mozilla has a command-line tool called web-ext that packages the files of an extension project directory into a zip file, omitting any content that is not needed in the final build, such as .git.
web-ext is a node-based application, and I didn't have Node.js installed on my Slackware laptop, so l headed on over to nodejs.org to download it. I simply input the commands exactly as displayed.
I then ran npm install --global web-ext to get the tool. To use it, I navigated to the project directory and ran web-ext run, which lets you easily test the extension, then web-ext build, which will create the .zip file in a new directory called web-ext-artifacts. This is the file to be uploaded to addons.mozilla.org. After uploading, it was simply a matter of waiting for approval!





