Jerome Thibaud logo
  • Home 
  • About Me 
  • Services 
  • Articles 
  • Contact 
  •    Toggle theme
    •   Light
    •   Dark
    •   Auto
  •  
    •   Light
    •   Dark
    •   Auto

How to build a chrome extension in 2024

Posted on May 26, 2024 • 9 min read • 1,727 words
Howto
 
Howto
 
Share via
Jerome Thibaud
Link copied to clipboard

This is a walkthrough on creating the 'Scrolling Zombie' chrome extension which tracks the amount of scrolling a user does to make them aware of their browsing behavior and help them fight dark UI patterns.

On this page
 

  • Problem Statement
  • Use Cases
  • Architecture
    • General Approach
    • Decisions on Privacy
    • Decisions on Usability
    • Decisions on Availability
    • Decisions on Scalability
    • Decisions on Performance
    • Components
    • Domain Model
    • Interactions
    • Deployment
    • Security
    • Privacy
  • Implementation
    • Technologies
    • Project Structure
    • Manifest file
      • Action popup UI
      • Content Script injected in browsed pages
      • The background service worker
      • Minimal Permissions
    • Testing
    • Deployment through the Webstore
      • Packaging
  • Observations and Gotchas
    • Time might not flow normally in inactive tabs
    • Scrollend not triggering
    • Permissions requirements for deployment to Web Store
    • When developing, refresh often
  • Conclusion
    • Future Direction

How to build a chrome extension in 2024

You can find this extension in the Chrome Web Store .

Problem Statement  

The Internet is now filled with attention vampires and other UI dark patterns. Many websites use these to capture your attention and keep it captive so that they can monetize it. A fair trade if you benefit from it but those practices aim to extract more than what they give.

One of those dark patterns is the “infinite scroll”. You experience it on LinkedIn, Amazon, Facebook, Instagram, TwitteX, etc. Ultimately, it is your choice how much time you want to spend scrolling through those websites. But faced with those tricks, your brain does not stand a chance, It needs a little help.

The goal of this solution is to make you aware of what you are doing and give you the opportunity to make the choice to stop and do something better with your life, or continue scrolling and get that next dose of dopamine.

Use Cases  

We’ll use the term Netizen to describe an internet user browsing websites.

  • As a Netizen, I want to be notified when I have scrolled too much so that I can decide that I should do something else.

  • As a Netizen, I want to see which sites I spend my time scrolling through so that I can evaluate the value I have gained in comparison to the time I spent.

  • As a Netizen, I want to decide how much scrolling represents too much scrolling so that I can be notified at a rate in line with my goals and expectations.

  • As a Netizen, I want to be notified of a crossed threshold while I am scrolling so that I can interrupt the behavior if it does not benefit me anymore.

  • As a Netizen, I want to be able to reset the stats so that I can measure my activity forward.

Use Cases

Architecture  

General Approach  

Leverage scroll events that are triggered by the browser when the user scrolls. Calculate time deltas and aggregate times between scrolling events. The scrolling information will be stored locally and displayed in the extension’s popup UI. The configuration will be stored locally, in the browser. A notification will be generated each time the accumulated scrolling time for a given page is greater than the sum of the configured threshold and the last notification.

Decisions on Privacy  

The data captured by the plugin is limited to the minimum No data leaves the user’s computer.

Decisions on Usability  

Sensible defaults when possible, for example for the initial Threshold setting.

Decisions on Availability  

A failure of the extension should not interfere with the browsing experience. A failure to register some scroll events can be ignored as accuracy is not critical. An order of magnitude is what we are trying to observe.

Decisions on Scalability  

Performance should not visibly degrade as the number of website tracked increases Storage is limited to 10MB by default which is more than enough for tracking pairs of website titles and integers durations (stored as strings).

Decisions on Performance  

Keep the computation to a minimum Use asynchronous operations whenever possible Performance should not visibly degrade as stats table grows

Components  

In the context of a google extension, there is a set architecture to follow. We will only leverage a subset of all capabilities.

Chrome Extension architecture context

When analyzing the set of use cases, we see a few components emerge. When we place them in a chrome extension context, we get the following:

Components Diagram

Domain Model  

The domain is very simple, the only interesting part might be the messages that will be exchanged between the components.

Messages Model Diagram

Interactions  

The Service Worker acts as the business logic service, counting and storing times.

  • The Content-Script injects a probe to relay scrolling events from the pages to the service worker.

    threshold setting interaction

  • The Popup is used to set the threshold setting.

    scroll tracking interaction

  • The Popup is used to set the threshold setting.

    scroll tracking interaction

Deployment  

The deployment is straightforward either through the Chrome Web Store or using offline deployment on the platforms that support it.

Deployment Diagram

Security  

Principle of Least privilege

In this context, this means that the extension only has the permissions necessary to perform its job.

Privacy  

No browsing data will be sent from the user’s computer. We will store everything in local storage.

Implementation  

Technologies  

  • Javascript
  • Chrome Extension Framework (Manifest V3)
    • Chrome Runtime
    • Chrome Storage
    • Chrome Notifications

Project Structure  

├── css
│   └── ...
├── fonts
│   └── ...
├── images
│   ├── icon128.png
│   ├── icon16.png
│   ├── icon32.png
│   └── icon48.png
├── popup
│   └── stats.html
├── scripts
│   ├── scroll_events_forwarder.js
│   ├── stats_service_worker.js
│   └── ...
└── manifest.json

Manifest file  

{
  "manifest_version": 3,
  "name": "Scrolling-Zombie",
  "description": "Be aware of the time you spent scrolling on those infinity-scroll websites",
  "version": "1.0",
  "action": {
    "default_popup": "popup/stats.html"
  },
  "icons": {
    "16": "images/icon16.png",
    "32": "images/icon32.png",
    "48": "images/icon48.png",
    "128": "images/icon128.png"
  },
  "content_scripts": [
    {
      "js": ["scripts/scroll_events_forwarder.js"],
      "matches": [
        "<all_urls>"
      ]
    }
  ],
  "background": { "service_worker": "scripts/stats_service_worker.js", "type": "module" },
  "permissions": [
    "storage",
    "notifications"
  ]
}

Action popup UI  

The following configures what html page will be loaded when the user clicks on the extension’s icon (action button)

...
  "action": {
    "default_popup": "popup/stats.html"
  },
...

In our case, that’s the ‘Statistics’ view of the extension and a couple actions (configure threshold, clear stats)

Deployment Diagram

Here’s the source for reference:

<html>
    <head>
        <link rel="stylesheet" href="/css/styles.css">
    </head>
    <body>
        <div id="info">
            <h1>Scrolling Zombie!</h1>
            <div id="buttons">
                <input id="clear" type="button" value="Clear stats!" />
            </div>
        </div>
        <div id="stats">
            <p id="threshold">Notify me every <input id="threshold-seconds" type="number" min="5" max="99999" value="60" step="5"/> seconds of active scrolling.</p>
            <p id="caption"><b>This is how much you scrolled!</b></p>
            <table id="stats-table">
            
            </table>
        </div>
        <div id="footer">
            <p>That's how they get you.</p>
        </div>
        <script type="module" src="/scripts/popup.js"></script>
    </body>
</html>

Content Script injected in browsed pages  

The scroll_events_forwarder.js script will forward all scroll and scrollend events from the scope of the current page to the service worker’s scope.

...
  "content_scripts": [
    {
      "js": ["scripts/scroll_events_forwarder.js"],
      "matches": [
        "<all_urls>"
      ]
    }
  ],
...

The background service worker  

The background process running in the browser that is listening to the events coming from the popup and the current page via the content-script. The “type”: “module” parameter allows for use of Ecmascript modules.

...
  "background": { "service_worker": "scripts/stats_service_worker.js", "type": "module" },
...

Minimal Permissions  

The access to Chrome’s APIs must be explicitly stated. We are using 2 apis beyond the default ‘runtime’ API: ‘storage’ and ’notifications’

  "permissions": [
    "storage",
    "notifications"
  ]

Testing  

  • Jest
  • Puppeteer with Jest as a runner

Testing will require a few additions to your project. They can be added as dev dependencies using npm.

npm install --save-dev <package_name>

Use that command for each the dependencies:

  • jest
  • jest-environment-jsdom
  • babel-jest
  • @babel/preset-env
  • puppeteer
The addition of babel in the mix is to allow the use of Ecmascript modules in the tests.

You should end up with the following in your project’s package.json file:

{
  "name": "scrolling-zombie",
  "version": "1.0.0",
  "description": "A chrome extension to help you keep track of your mindless scrolling.",
  ...
  "devDependencies": {
    "@babel/preset-env": "^7.24.5",
    "babel-jest": "^29.7.0",
    "install": "^0.13.0",
    "jest": "^29.7.0",
    "jest-environment-jsdom": "^29.7.0",
    "puppeteer": "^22.10.0"
  }
  ...
}

This expands our project structure to the following:

├── css
│   └── ...
├── fonts
│   └── ...
├── images
│   └── ...
├── popup
│   └── ...
├── scripts
│   └── ...
├── tests
│   └── ...
├── manifest.json
├── jest.config.js
├── babel.config.js
├── mock-extension-apis.js
├── package-lock.json
└── package.json

You can then configure node to run your tests by adding the following scripts section in your package.json file

{
  "name": "scrolling-zombie",
  "version": "1.0.0",
  "description": "A chrome extension to help you keep track of your mindless scrolling.",
  ...
  "scripts": {
    "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
    "test-integration": "node --experimental-vm-modules node_modules/jest/bin/jest.js tests/integration.test.js"
  },
  ...
}
The use of –experimental-vm-modules allows for Ecmascript module support in Jest.

Deployment through the Webstore  

Packaging  

Packaging the chrome extension consist in create a zip archive with the resources required at runtime and uploading it through the developer portal.

#!/bin/bash
rm scrolling-zombie.zip
zip -r scrolling-zombie.zip css fonts images popup scripts LICENSE.txt manifest.json README.md

The Scrolling Zombie archive has the following content:

.
├── LICENSE.txt
├── README.md
├── css
│   └── styles.css
├── fonts
│   └── ZOMBIE.woff
├── images
│   ├── icon128.png
│   ├── icon16.png
│   ├── icon32.png
│   ├── icon48.png
├── manifest.json
├── popup
│   └── stats.html
└── scripts
    ├── event_messages.js
    ├── human_readable.js
    ├── popup.js
    ├── scroll_events_forwarder.js
    ├── settings.js
    ├── stats.js
    └── stats_service_worker.js

Observations and Gotchas  

Time might not flow normally in inactive tabs  

It is worth noting that Chrome does not guarantee consistent time for tabs that are in the background and may have been put to sleep. I was initially relying on timestamps generated on the page by the content-script but observed that timestamps in the past were being sent under some conditions. I found a few relevant pieces of information, for example this chrome developers article on background tabs

Scrollend not triggering  

I was initially using the ‘scrollend’ event but found out that it is not always produced when you would expect it. For example, when pressing Page Down and reaching the bottom of the page, the scrollend event is not generated. If using the scrolling wheel on the mouse, it is generated.

Permissions requirements for deployment to Web Store  

The Web Store enforces a few rules when submitting for publication. Some have to do with the validation of the manifest file from a permission standpoint. There’s a requirement that no unused permission be configured. I found it hard to assess this based on the documentation and without attempting to submit.

When developing, refresh often  

Remember that the communication between the content-script, popup and service-worker will be broken every time you reload the extension. All pages that were open before the extension reload will need to be reloaded themselves for their injected content-script to reconnect with the extension’s service-worker.

Conclusion  

Here’s a link to the extension in the Web Store .

Future Direction  

After a few days of using this extension, I was happy to be yanked out of my scrolling daze in a few occasions and I am already thinking about how it could be improved.

for ex:

  • An exclusion list for pages we don’t want to be notified on
  • Some limitation on the number of sites displayed in the stats table
  • Maybe per domain time aggregation instead of per page
 How to deal with the Frankenstein Software Monster
Onboarding as a Senior Engineer 
On this page
  • Problem Statement
  • Use Cases
  • Architecture
    • General Approach
    • Decisions on Privacy
    • Decisions on Usability
    • Decisions on Availability
    • Decisions on Scalability
    • Decisions on Performance
    • Components
    • Domain Model
    • Interactions
    • Deployment
    • Security
    • Privacy
  • Implementation
    • Technologies
    • Project Structure
    • Manifest file
      • Action popup UI
      • Content Script injected in browsed pages
      • The background service worker
      • Minimal Permissions
    • Testing
    • Deployment through the Webstore
      • Packaging
  • Observations and Gotchas
    • Time might not flow normally in inactive tabs
    • Scrollend not triggering
    • Permissions requirements for deployment to Web Store
    • When developing, refresh often
  • Conclusion
    • Future Direction
Let's Connect

Collaborate and create user value

       
Copyright © 2024 Jerome Thibaud. Licensed under Creative Commons (CC BY-NC-SA 4.0). Powered by Hinode  .
Jerome Thibaud
Code copied to clipboard