A few weeks in the past, I spent the weekend creating one other CFP submission helper within the type of a Firefox extension. It was not a stroll within the park. To assist others who could also be involved in doing the identical (and my future self), this is my journey.
Context
I’ve written a number of posts about my conference submission workflow. To sum up:
- The whole lot relies on a Trello board
- I created an app that registered a webhook on the board
- After I transfer a convention from one lane to a different, it begins or continues a workflow on the app aspect
I supply the board by web sites, primarily Papercall and Sessionize, and manually copying convention knowledge on playing cards. Two automation choices can be found:
- Automating convention sourcing
- Automating a card creation
I believed lengthy and arduous concerning the first half. If I automate it, it is going to create an extended record of Trello playing cards, which I am going to must filter anyway. I concluded that it is higher to filter them earlier than.
Nevertheless, I created the cardboard manually by copy-pasting related knowledge: identify, dates, due date, CFP hyperlink, and web site. It is exactly what a Firefox extension may also help one with.
Necessities and design
The person story is fairly simple:
AS A: Lazy developer
I WANT TO: Routinely add CFP knowledge on Trello whereas shopping an online web page on Papercall or Sessionize
SO AS: To spend my time doing extra enjoyable stuff than copy-paste— My single person story
My solely requirement is that it must work with Firefox.
My first thought is a button to set off the creation, however I do not care a lot the place it’s: contained in the web page as an overlay or someplace on the browser. Within the first case, it must be a JavaScript injected client-side; on the opposite, a Firefox extension. I selected the second possibility as a result of I wanted to determine find out how to obtain the primary.
I additionally needed first to create my extension in Rust with WebAssembly. Spoiler: I did not.
A easy Firefox extension
I had no clue about writing a Firefox extension, as this was the primary time I did write one. My first step was to observe the tutorial. It explains the fundamentals of an extension construction. Then, I adopted the second tutorial. It explains find out how to create a pop-up menu for the extension however not find out how to work together with the net web page. At this level, I made a decision to be taught by doing, a way that works nicely for me.
A Firefox extension begins with a manifest. Here is the one from the primary tutorial, simplified:
{
"manifest_version": 2,
"identify": "Borderify",
"model": "1.0",
"content_scripts": [
{
"js": ["borderify.js"]
}
]
}
doc.physique.fashion.border = '5px strong crimson';
I discovered the event suggestions loop good. Think about that you’ve adopted the tutorial and created all the required information above. You possibly can go to about:debugging#/runtime/this-firefox and click on on the “Load Short-term Add-on” button.
Then, level to your manifest file. Firefox masses the extension: it is now lively.
Within the above instance, the JavaScript from the tutorial provides a crimson border round each internet web page. It is ineffective, we are able to do higher, however it reveals the way it works. We will change the script to alter the colour, e.g., from crimson
to inexperienced
. To make Firefox reload any change, together with modifications to the manifest, click on on the “Reload” button on the short-term extension panel.
Interacting with the extension
As I discussed above, I need a button to set off the creation of the Trello Card. Firefox permits a number of interplay choices: direct set off or opening of a pop-up window. I needn’t enter any parameter, so the previous is sufficient in my case.
Firefox permits a number of locations so as to add buttons: the browser’s toolbar, a sidebar, or contained in the browser’s URL bar. I used the toolbar for no purpose than as a result of it was what the second tutorial demoed. In the end, it solely modifications a bit, and transferring from one to a different is simple.
Including the button takes place within the manifest:
"browser_action": {
"default_area": "navbar", #1
"default_icon": "icons/trello-tile.svg" #2
}
- Set the button on the navigation bar. For extra particulars on the button location, please verify the documentation
- Configure the icon. One can use bitmaps in numerous codecs, however it’s a lot simpler to set an SVG
At this level, every thing was positive and dandy. Afterward, I misplaced many hours making an attempt to know the completely different sorts of scripts and the way they work together. I am going to make it a devoted part.
Scripts, scripts in all places
The default language for scripts in extensions is JavaScript. Nevertheless, relying on their location, they play completely different roles. Worse, they should “speak” with each other.
Let’s begin with the content-script
I used within the above manifest.json
. Content material scripts are certain to an online web page. As such, they will entry its DOM. They run when Firefox masses the web page. The script provides a crimson border across the internet web page’s physique
within the tutorial.
Nevertheless, we want one other form of script: one to set off after we click on on the button. Such scripts ought to run together with the extension however can hearken to occasions. They’re often called background
scripts.
Background scripts are the place to place code that should preserve long-term state, or carry out long-term operations, independently of the lifetime of any explicit internet pages or browser home windows.
Background scripts are loaded as quickly because the extension is loaded and keep loaded till the extension is disabled or uninstalled, except persistent is specified as false. You should use any of the WebExtension APIs within the script, so long as you’ve requested the required permissions.
Let’s create such a script. It begins with the manifest
– as typical:
"background": {
"scripts": [ "background.js" ]
}
We will now create the script itself:
operate foo() {
console.log('Hiya from background')
}
browser.browserAction.onClicked.addListener(foo) //1
- Register the
foo
operate as an occasion listener to the button. When one clicks the extension button, it calls thefoo
operate
Debugging the extension
Let’s cease for a second to speak about debugging. I misplaced a number of hours as a result of I did not know what had occurred. After I began to develop JavaScript 20 years in the past, we “debugged” with alert()
. It was not the most effective developer expertise you would hope for. Extra fashionable practices embrace logging and debugging. Spoiler: I did not handle to get debugging working, so I am going to deal with logging.
First issues first, content material scripts work within the context of the web page. Therefore, logging statements work within the common console. Background scripts do work in one other context. To observe their log statements, we have to have one other Firefox developer console. You possibly can open it on the extension panel by clicking the “Examine” button.
Communication throughout scripts
Now that we all know find out how to log, it is potential to go additional and describe communication throughout scripts. Here is an outline of the general stream:
Let’s change the code a bit in order that background.js
sends a message:
operate sendMessage(tab) {
browser.tabs
.sendMessage(tab.id, 'message in from background')
.then(response => {
console.log(response)
})
.catch(error => {
console.error(`Error: ${error}`)
})
}
browser.browserAction.onClicked.addListener(sendMessage)
Now, we alter the code of content material.js
:
browser.runtime.onMessage.addListener((message, sender) => {
return Promise.resolve('message again from content material')
});
Getting the content material
To this point, we have now carried out a back-and-forth stream between the background
and the content material
scripts. The meat is to get content material from the web page within the content material
script and cross it again to the background
through a message. Keep in mind that solely the content material
script can entry the web page! The code itself makes use of the Doc API, e.g., doc.querySelector()
, doc.getElementsByClassName()
, and many others. Specifics are unimportant.
The subsequent subject is that the construction of Sessionize and Papercall are completely different. Therefore, we want completely different scraping codes for every web site. We may develop a single script that checks the URL, however the extensions can maintain it for us. Let’s change the manifest:
"content_scripts" : [{
"matches": [ "https://sessionize.com/*" ], #1
"js": [ #2
"content/common.js", #4
"content/sessionize.js"
]
},
{
"matches": [ "https://www.papercall.io/*" ], #1
"js": [ #3
"content/common.js", #4
"content/papercall.js"
]
}]
- Match completely different websites
- Scripts for Sessionize
- Scripts for Papercall
- Code shared on each websites
At this level, we managed to get the required knowledge and ship it again to the background
script. The final step is to name Trello with the information.
Dealing with authentication credentials
Utilizing Trello’s REST requires authentication credentials. I need to share the code on GitHub, so I can’t hard-code credentials: I want configuration.
We will configure a Firefox extension through a devoted choices web page. To take action, the manifest presents a devoted options_ui
part the place we are able to present the trail to the HTML web page:
"options_ui": {
"web page": "settings/choices.html"
}
The web page can instantly reference the scripts and stylesheet it wants. The script must:
- Retailer credentials within the browser storage on save
- Load credentials from the browser storage when the settings web page opens
It is fairly simple with the provided example.
My code is kind of related; it simply wants three fields as a substitute of 1:
operate saveOptions(e) {
browser.storage.sync.set({ //1
listId: doc.querySelector('#list-id').worth,
key: doc.querySelector('#key').worth,
token: doc.querySelector('#token').worth,
})
}
operate restoreOptions() {
browser.storage.sync.get() //1
.then(knowledge => , error => {
console.error(`Error: ${error}`)
})
}
doc.addEventListener('DOMContentLoaded', restoreOptions) //2
doc.querySelector('kind').addEventListener('submit', saveOptions) //3
- Makes use of the Firefox
storage
API - Learn from the storage when the web page masses
- Save to the storage when the person submits the HTML
kind
We additionally must ask the storage
permission within the manifest:
"permissions": [ "storage" ]
We will now retailer the Trello credentials (in addition to the required Trello record id) on the settings web page:
We will use the identical storage
API within the Trello calling code to learn credentials.
At this level, I used to be pleased with my setup. I simply added one other round-trip from the background
to the content material
to show an alert
with Trello’s card identify and URL.
Conclusion
It was the primary extension I wrote, and although the start was difficult, I achieved what I needed. Now, I can navigate to a Papercall and a Sessionize web page, click on the extension button, and get the convention on my Trello board. It took me a few days and was enjoyable; it was nicely value it. I proceed engaged on it to enhance it little by little.
The entire supply code for this publish will be discovered on GitHub:
To go additional:
Initially printed at A Java Geek on April 2nd, 2023