How I create my first Chrome extension with Github Copilot
Tags: javascript, extensions, githubcopilot
Published: 2024-03-06 09:33:08

My story is that I'm learning Japanese on a website called Dungmori.com, and I study new vocabulary on the Quizlet website. I encountered difficulties in copying vocabulary from Dungmori to Quizlet because I had to copy each word one by one. Previously, I used to copy sets of vocabulary created by other Quizlet users, then manually checked for any missing words compared to Dungmori and added them myself. This method consumed a lot of my time. Later, I discovered that Quizlet allows importing multiple words with various formatting options. For example, two words separated by a comma, tab, space, or two cards separated by line breaks \n, \r\n, ...

![Quizlet Set import UI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i6h313z2dstadc80s5vs.png)

Meanwhile, on Dungmori, they present new words using a standard `<table>` tag, which allows me to easily use JavaScript to transform the text within this table into a text string for importing into Quizlet.

![Dungmori Vocab table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/52x6na6gcus2wkgrq1zp.png)

But do I have to search for the code, open DevTools, and paste the code into the console tab every time I want to fetch vocabulary from this table? This question led me to consider creating an extension to install on the browser. However, I've never written an extension before, I only know HTML, CSS, and JavaScript. Perhaps GitHub Copilot can help me with that, right?

![So f**king tired developer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xuwg8rbwtrffmzyd95d4.jpeg)

So, I began asking Copilot how to create an extension from scratch. The answer was that I needed to create a file called `manifest.json` to store the important configurations of the extension, a file named `popup.html` to design the interface for the extension, another file called `popup.js` to write the logic, and a file named `content.js` to interact with the DOM elements of the website. Copilot also guided me on how to import a code folder into the browser by enabling Developer mode in the extension settings, then selecting the code folder and reloading the browser.
```json
// manifest.json
{
  "manifest_version": 2,
  "name": "My First Chrome Extension",
  "version": "1.0",
  "description": "A simple Chrome extension",
  "browser_action": {
    "default_icon": "icon.png",
    "default_popup": "popup.html"
  },
  "permissions": ["activeTab"],
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ]
}
```
```html
<!-- popup.html -->
<!DOCTYPE html>
<html>
  <body>
    <h1>Hello, this is my first Chrome extension!</h1>
    <script src="popup.js"></script>
  </body>
</html>
```
I started writing the logic into the `content.js` file to convert the text in the table into a string that can be imported into Quizlet and then copied to the clipboard. I won't delve much into this part because it involves basic DOM operations. Afterward, I ran a test, and voila, the logic ran as soon as the web page loaded. However, I wanted to trigger it by clicking a button in the extension popup. That way, it would require more steps to use, but it would be cooler `¯_(ツ)_/¯`. Why not?
Then, I turned to Copilot again and received the answer that this configuration is in the `manifest.json` file.
```json
"content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ]
```
The solution? Delete that section of the config, and manually trigger it using the `chrome.scripting.executeScript()` method
```js
chrome.tabs.query({active: true, currentWindow: true}, 
    function(tabs) {
        chrome.scripting.executeScript({
            target: {tabId: tabs[0].id},
            files: ['content.js']
        }).then(() => {
            console.log('Injected the content script.');
        }).catch(err => console.error(err));
    });
```
Yeah, and now the `content.js` file can be triggered when I click the button in the extension popup, but the copy to clipboard function isn't working as before. Once again, the almighty Copilot came to my rescue, providing the answer that:
> The error "Document is not focused" occurs because the `document of` the background page or popup loses focus when you switch to the content page, and the Clipboard API requires the document to be focused to write to the clipboard.

And the solution is that I need to send the text that needs to be copied from the `content.js` file to the `popup.js` file using a method that I'm not sure about, but the idea seems similar to a pub/sub mechanism.
```js
// content.js
chrome.runtime.sendMessage({type: "copy", text: normalizedRows.join("\r\n")});

// background.js or popup.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    if (request.type === "copy") {
        navigator.clipboard.writeText(request.text).then(function() {
            console.log('Text copied to clipboard');
        }).catch(function(err) {
            console.error('Could not copy text: ', err);
        });
    }
});
```

BOOOOM! THE RESULT IS HERE!
![Result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kvch2uz462q1kyvbqata.png)

Now, I can copy vocabulary from Dungmori into the clipboard with just one click. Thanks to Copilot, it took me less than an hour to create this extension. Imagine if I had to search Google to do this; I would have to open a billion tabs every time an issue arises. But with Copilot, I just stay put in my VS Code window. Previously, I tried with ChatGPT and Gemini, but the output didn't come close to this.

![100000000000000 tabs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ujd10ozp2r0qbf857vv6.jpeg)
Above is not some high-tech tech sharing article; it's just my simple story and fun experience with Copilot. Also, thanks to Dall-E for helping me create illustrations, and ChatGPT for making my English translations sound less like a robot. Peace, I'm out.

GitLab Repository: https://gitlab.com/1337NamNori/copy-vocab-extension