How to build a Chrome Extension to analyze the text as you write (2023)

Build your own chrome extension to count words, characters, and price-per-word as you write

How to build a Chrome Extension to analyze the text as you write (1)

Not long ago, I started writing on Medium as a part-time gig to alternate with my freelance work. Not being a native English speaker, I found using chrome extensions like Grammarly really helpful. (In case you don’t know it yet, it is a digital writing tool that uses artificial intelligence and natural language processing to help with grammar checking, spell checking, plagiarism detection and suggestions about clarity, formality, tone, and vocabulary). Anyway, I don’t use all of its features because it has different plans (Free, Premium for individuals and Premium for business) or at least I didn’t have the necessity of using those features yet. So I thought of developing my own chrome extension implementing only the features I need, in order to learn the possibilities this technology offers.

Extensions are not only for Google Chrome though. Other popular browsers like Microsoft Edge and Mozilla Firefox support extensions as well, but extensions are the same for everyone: they are small applications built using HTML, CSS, and JavaScript, packaged in a specific way along with configuration files.
They need to follow the Web Extension API standard, which is supported by Chromium-based browsers such as Google Chrome, Microsoft Edge, Mozilla Firefox, and Microsoft Edge.

What’s a Chrome Extension for?

Chrome extensions are not only for text analysis. They can interact with the tabs of your Chrome browser using permissions, detect URL matches, inject code (HTML, JavaScript, CSS for example), do API calls, and so on.
If we give a look to the Chrome Web Store, we can check all the different categories:

How to build a Chrome Extension to analyze the text as you write (2)

We will build an extension to count words and characters on a text field or input as we type, and to calculate the total of money you will gain (For freelance writers, for example) setting the price per word (PPW). We will call it ChETA, which stands for Chrome Extension for Text Analysis, which in my native Argentina means cool/awesome.

Our extension will look like the following, while opened from the Extensions tab:

How to build a Chrome Extension to analyze the text as you write (3)

We will ask the user to locate the input he wants to use. Then, he will need to write <cheta> (add it anywhere on the input or replacing the content, it’s the same) on it and click Start. Optionally, he will be able to set a price per word (PPW from now on).

For example, on Google’s input:

How to build a Chrome Extension to analyze the text as you write (4)

And when clicking Start, we will inject into the page HTML code the things we need to start listening inputs change using a floating container:

How to build a Chrome Extension to analyze the text as you write (5)

Extensions are made of different components that interacts with each other. Components can include background scripts, content scripts, an options page, UI elements, and various logic files. Extensions components are created with web development technologies: HTML, CSS, and JavaScript. An extension’s components will depend on its functionality and may not require every option.

  1. manifest.json: Describes what’s in the source package. It defines where the browser could find the background, content script, popup, and options pages. It also describes the permissions required by the extension (For example to allow the extension to check all tabs, only current tab, use storage, and so on)
  2. background.js: A script or piece of code that is launched when the extension starts, and won’t be terminated until the extension is removed or the browser shutdowns. This file has access to all chrome APIs, and other parts are limited to it. This script does not include a UI and does not have access to the DOM.
  3. popup.html: The UI which is displayed when the user clicks on ‘Browser Action’, which is the button right to the browser address bar where the extensions are located. Most extensions need a popup as entry, but they can also be developed to be called using right-click on pages.
  4. options: It’s an optional part of the extension, which not all extensions include. It is used as a configuration UI for the extension, in order to enable multiple views.
  5. content script: A script or piece of code that runs in a tab with a specific URL pattern, defined in manifest.json. If the URL matches with the manifest description, the browser will launch the content script. It will be terminated when the URL changes or when the tab closes. It is needed to manipulate the DOM.
    URL matching can be useful when we need to launch our script on specific URLs or specific instances of a web flow.

For this tutorial, we will use React.js to build the extension. The source code is available on Github (https://github.com/juancurti/cheta-extension) to follow along.

First, we will create the react app and remove unnecessary files we won’t use:

npx create-react-app cheta-extension
cd cheta-extension/
cd src/
rm -rf setupTests.js serviceWorker.js logo.svg index.css App.test.js App.js App.css

We will install node-sass, which is not mandatory but it will help us write quicker CSS code:

npm i node-sass — save

Now we will replace the manifest.json located in the public/ folder, which is related to React.js, for the following code corresponding to the manifest.json concept explained above:

{
“name”: “ChETA: Chrome Extension for Text Analysis”,
“version”: “1.0.0”,
“manifest_version”: 2,
“description”: “ChETA: Chrome Extension for Text Analysis”,
“icons”: {
“512”: “logo512.png”
},
“permissions”: [“activeTab”],
“browser_action”: {
“default_icon”: “logo512.png”,
“default_popup”: “popup.html”
}
}

Note: The logo512.png file can be found on the repository: Link here

As explained before, the initial point of a Chrome Extension should be a popup.html, so we will create a build script to rename our index.html generated by React build to popup.html.
We will add a new file: script/build.sh, with the following content:

#!/bin/bashbuild() {
echo ‘building react’
rm -rf dist/*export INLINE_RUNTIME_CHUNK=false
export GENERATE_SOURCEMAP=false
react-scripts buildmkdir -p dist
cp -r build/* dist
mv dist/index.html dist/popup.html
}
build

Important to know!

INLINE_RUNTIME_CHUNK is set to false in order to disable webpack inline JavaScript generation in HTML since normally it will put its own runtime into the script, which is not allowed by the browser extension standards.

We need to add permissions to the script using the following command:

chmod +x script/build.sh

And finally, we need to modify our package.json file to start building with our script instead of react-scripts. Replace the build script for the following:

"scripts": {
"build": "./script/build.sh"
},

As stated before, we will use node-sass instead of simple CSS, just to write fewer lines for the sake of the tutorial.
Create a new file src/styles/styles.scss with the following content:

@import url('https://fonts.googleapis.com/css2?family=Special+Elite&display=swap');
html{
width:100%;
}
body{
margin: 0px;
height: 400px;
width: 280px;
font-family: 'Special Elite', sans-serif;
background: white;
.header {
width: 280px;
height: 50px;
margin: 0px;
background-color: #B0FF8B;
padding: 12px 0px;
h1 {
font-family: 'Special Elite';
font-size: 28px;
opacity: 0.9;
margin: 0px 4px 0px 4px;
font-weight: 500;
}
p {
margin: 0px 4px;
font-family: 'Special Elite';
font-weight: 400;
font-size: 14px;
}
input, #priceperwordInput {
text-align: center;
font-family: 'Special Elite';
font-weight: 400;
font-size: 14px;
}
#priceperwordInput {
width: 80px;
}
}
.footer {
width: 280px;
height: 40px;
margin: 0px;
background-color: #B0FF8B;
padding: 6px 0px;
text-align: center;
a {
text-decoration: none;
}
}
.contentBox {
width: 280px;
height: 310px;
.resalted {
background-color: rgba(176, 255, 139, 0.7);
text-decoration: underline;
}
.no-tf-dtctd {
margin: 0 10px;
text-align: center;
font-size: 14px;
position: relative;
top: 50%;
transform: translateY(-50%);
a {
text-align: right;
}
}
}
}

Now let’s clean our index.js file and set an entry point for our extension:

import React from 'react';
import ReactDOM from 'react-dom';
import './styles/styles.scss';
import Home from './Home';
ReactDOM.render(
<React.StrictMode>
<Home />
</React.StrictMode>,
document.getElementById('root')
);

Now let’s create our component. We will need to add /* global chrome*/ at the top of our file in order to prevent ESLint to detect it as an error. Create src/Home.js with the following format:

/*global chrome*/
import React, { Component } from 'react';
class Home extends Component {}export default Home;

We will add a constructor now. The variables we are going to use are basically two:

  1. uniqueCode: This is going to be the string the user will need to type in order to detect the working input. Having this variable in the state allows us to play with it according to our needs; for instance, if we want to make a paid version of this app, this code could be valid for a duration of 30 minutes and then the user would need to top up his account to continue using it.
  2. inputs: With this map, we will take care of the inputs of our React application. I’m using a map instead of a variable to be able to scale up in case other text fields are needed.
constructor(props){
super(props);
this.state = {
uniqueCode: `<cheta>`,
inputs: {
priceperword: '0.05'
}
}
}

Also, we need to add a helper function to detect input changes (In our chrome extension, not in the DOM) to update our price per word variable.

handleInputChange = event => {
const { name, value } = event.target;
var inputs = this.state.inputs;
inputs[name] = value;
this.setState({
inputs: inputs
})
}

Now we will add the render() method, which is mostly HTML. We already added the styles (With the .scss file), now we will add the structure as the following diagram:

How to build a Chrome Extension to analyze the text as you write (6)

You can copy and paste it from here, which will be faster:

render() {
return (
<div>
<div className="header">
<h1>ChETA</h1>
<p>Chrome Extension for Text Analysis</p>
</div>
<div className="contentBox">
<div className="no-tf-dtctd">
<p>To start analysing a textfield, <span className="resalted">follow the steps</span>:</p>
<p>1. <span className="resalted">Locate the field</span> you want to analyze</p>
<p>2. Replace or <span className="resalted">add</span> anywhere on the field <span className="resalted">the following code</span>:</p>
<p>Code: <span className="resalted">{this.state.uniqueCode}</span></p>
<p>3. When ready, click start</p>
<a href="#" onClick={this.nextTapped_det}>Start</a>
<hr/>
<p>Price per word (Optional): </p><input id="priceperwordInput" name="priceperword" value={this.state.inputs.priceperword} onChange={this.handleInputChange} type="number" step="0.01"/>
</div>
</div>
<div className="footer">
<a href="https://juancurti.com" target="_blank"><p>Juan Curti - 2020</p></a>
</div>
</div>
)
};

Last thing here, we need to add our nextTapped_det method, which will be called when pressing the Start button. Here, we will do a few things:

  1. Declare a config map, which will be sent to the targeted DOM. This is really important if we want to send dynamic data from our extension to the DOM. Into this map, we will add the unique code that our script will try to find and the price per word, only if it is higher than 0.00; if it’s not, then we won’t display the price per word counter since it will be always zero.
  2. Then we declare a CSS file to inject. We are sending also a custom font using the @import method. Making use of the chrome API, we use the insertCSS method to inject it.
  3. Execute a custom script we will write later on, but only after declaring our config variable.

The code for the nextTapped_det method is the following

nextTapped_det = () => {
var config = {
code: this.state.uniqueCode
};
if(this.state.inputs.priceperword > 0) {
config['priceperword'] = this.state.inputs.priceperword;
}
var css = "@import url('https://fonts.googleapis.com/css2?family=Special+Elite&display=swap'); #cheta-flt-dv { padding: 8px; z-index: 999; position: fixed; width: 140px; bottom: 40%; right: 40px; background-color: #B0FF8B; color: black; border-radius: 20px; text-align: center; box-shadow: 2px 2px 3px #999; } .cheta-flt-p { margin: 2px; font-family: 'Special Elite'; font-size: 22px; } .cheta-pfnt { margin: 2px; font-family: 'Special Elite'; font-size: 14px; }";
chrome.tabs.insertCSS({code: css});
chrome.tabs.executeScript({
code: 'var config = ' + JSON.stringify(config)
}, function() {
chrome.tabs.executeScript({
file: 'chetalib/chetalib.js'
});
})
}

Finally, we need to add the script we will use to detect the input, listen to changes, and start analyzing the text. In case there is no text field with the code we are looking for, we will terminate the script. Also, we will make use of a helper function to detect not only inputs but also text areas, which are the most common scenarios.

Let’s create in our public folder chetalib/chetalib.js, outside of our src/, with the following content:

function init() {
console.log("Initializing ChETA");
var link = document.createElement('link');
link.setAttribute('rel', 'stylesheet');
link.setAttribute('type', 'text/css');
link.setAttribute('href', 'https://fonts.googleapis.com/css2?family=Special+Elite&display=swap');
document.head.appendChild(link);
if(!config.code) {
console.log("Error loading ChETA: Unique code not found")
return;
}
var inputs = getInputsByValue(config.code);
if(inputs.length == 0) {
console.log("Error loading ChETA: Input not found")
return;
}
var text = inputs[0].value.split(config.code).reverse()[0];
// alert(text)
inputs[0].value = inputs[0].value;
inputs[0].addEventListener('input', (e) => {
var inputs = getInputsByValue(config.code);
if(inputs.length == 0) {
console.log("Error loading ChETA: Input not found");
return;
}
var text = inputs[0].value.split(config.code).reverse()[0];
var wordCountSpan = document.getElementById("cheta-data-wordcount");
wordCountSpan.textContent = ""+text.split(" ").length;
var charCountSpan = document.getElementById("cheta-data-charcount");
charCountSpan.textContent = ""+text.length;
if(config.priceperword) {
var pricePerWord = document.getElementById("cheta-data-priceperword");
var priceVal = text.split(" ").length * config.priceperword;
pricePerWord.textContent = ""+priceVal.toFixed(2);
}
});
const div = document.createElement('div');
var finalHTML = '<div id="cheta-flt-dv"><p class="cheta-flt-p">ChETA</p>';
finalHTML += '<p class="cheta-pfnt">Words: <span id="cheta-data-wordcount">'+text.split(" ").length+'</span></p>';
finalHTML += '<p class="cheta-pfnt">Chars: <span id="cheta-data-charcount">'+text.length+'</span></p>';
if(config.priceperword) {
var priceVal = text.split(" ").length * config.priceperword;
finalHTML += '<p class="cheta-pfnt">Total PPW: $<span id="cheta-data-priceperword">'+priceVal.toFixed(2)+'</span></p>';
}
finalHTML += '</div>';
div.innerHTML = finalHTML;
document.body.appendChild(div);
}
function getInputsByValue(value)
{
var allInputs = document.getElementsByTagName("input");
var results = [];
for(var x=0;x<allInputs.length;x++)
if(allInputs[x].value.includes(value))
results.push(allInputs[x]);
var allTextArea = document.getElementsByTagName("textarea");
for(var x=0;x<allTextArea.length;x++)
if(allTextArea[x].value.includes(value))
results.push(allTextArea[x]);
return results;
}
init();

Now that we’re done let’s test our extension. First, we need to build our project, we will generate the dist/ folder. To do this, let’s run on our project folder:

npm run build

If everything went good, we should see this message in our command line:

How to build a Chrome Extension to analyze the text as you write (7)

Let’s open Google Chrome now and go to chrome://extensions. At the top right of you screen, you should see the option to enable Developer mode. Let’s activate it:

How to build a Chrome Extension to analyze the text as you write (8)

Now you’re ready to upload it! Click the Load unpacked button, look for your dist/ folder, and load it. Finally, as all extensions, you only need to make it visible clicking this little button:

How to build a Chrome Extension to analyze the text as you write (9)

Finally, to test it we can go to shrib.com, activate our extension, and see the result:

How to build a Chrome Extension to analyze the text as you write (10)

Developing a Chrome Extension is not as hard as people could think. And being in JavaScript, the possibilities are infinite.

As homework, you can try implementing Sentiment Analysis (Explained in another of my tutorials, Link here) in order to detect the tone of the text (Positive — negative), or implementing Google Vision API to read text on images on a given webpage out loud for blind people (Explained here).

Top Articles
Latest Posts
Article information

Author: Foster Heidenreich CPA

Last Updated: 11/09/2022

Views: 5479

Rating: 4.6 / 5 (76 voted)

Reviews: 91% of readers found this page helpful

Author information

Name: Foster Heidenreich CPA

Birthday: 1995-01-14

Address: 55021 Usha Garden, North Larisa, DE 19209

Phone: +6812240846623

Job: Corporate Healthcare Strategist

Hobby: Singing, Listening to music, Rafting, LARPing, Gardening, Quilting, Rappelling

Introduction: My name is Foster Heidenreich CPA, I am a delightful, quaint, glorious, quaint, faithful, enchanting, fine person who loves writing and wants to share my knowledge and understanding with you.