Web Browser Extension Workshop - Part 5

This is the last post in the series of the Web Browser Extension Workshop. In this post, we will populate our popup using the service we created in the previous post.

Overview

In the last 4 posts, we have created a web browser extension that parses the OpenGraph data from a Webpage on the active tab and stores the information in a database (IndexedDB). We have also created a service that communicates with the database.

In this post, we will create a popup that will display the OpenGraph data of the active tab, if available.

As a reminded, the code is available on ogpatrol GitHub repository.

Popup: HTML

The popup HTML is trivial:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>ogpatrol</title>
    <meta name="manifest.type" content="browser_action" />
  </head>
  <body>
    <div id="app">
      <h1>OG Patrol</h1>
      <div id="message"></div>
      <div id="ogImage"></div>
      <div id="ogProps"></div>
    </div>
    <script type="module" src="./main.ts"></script>
  </body>
</html>

We create 3 divs to manipulate the content of the popup. The message div will be used as a notification area. The ogImage div will display the OpenGraph image, if available. The ogProps div will display the OpenGraph properties we have parsed.

Popup: CSS

The CSS is also trivial:

:root {
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
  line-height: 1.5;
  font-weight: 400;

  color-scheme: light dark;
  color: rgba(255, 255, 255, 0.87);
  background-color: #242424;

  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-text-size-adjust: 100%;
}

@media (prefers-color-scheme: light) {
  :root {
    color: #213547;
    background-color: #ffffff;
  }
}

body {
  margin: 0;
  display: flex;
  min-width: 320px;
  min-height: 100vh;
}

h1 {
  font-size: 2em;
  line-height: 1.1;
}

#app {
  max-width: 1280px;
  margin: 0 auto;
  padding: 1rem;
}

This is pretty much a simplified version of the CSS generated by the wxt tool.

Popup: TypeScript

The TypeScript code is where the magic happens:

import "./style.css";

// Get HTML elements of interest via declaration:
declare const message: HTMLDivElement;
declare const ogImage: HTMLDivElement;
declare const ogProps: HTMLDivElement;

// Get our service:
const SERVICE = getService();

// Query the active tab and work on it:
browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
  // Clear content slots:
  message.innerHTML = "";
  ogImage.innerHTML = "";
  ogProps.innerHTML = "";

  // We should have a single tab here:
  const tab = tabs[0];

  // Make sure that we have a tab:
  if (!tab) {
    return;
  }

  // Attempt to get the tab URL;
  const url = tab.url ?? tab.pendingUrl;

  // Make sure that we have a URL:
  if (!url) {
    return;
  }

  // Attempt to find and render the record for the URL:
  renderOpenGraphData(url);
});

async function renderOpenGraphData(url: string) {
  // Show loading message:
  message.innerHTML = "Loading...";

  // Find record for the current page:
  const record = await SERVICE.find(url);

  // Remove loading message:
  message.removeChild(message.firstChild!);

  // Check if we have a record:
  if (!record) {
    message.innerHTML = "No OpenGraph record found for this page.";
    return;
  }

  // Render images if any:
  record.ogdata.ogImage?.forEach((image) => {
    const img = document.createElement("img");
    img.src = image.url;
    img.alt = image.alt ?? "";
    ogImage.appendChild(img);
  });

  // Render properties:
  const pre = document.createElement("pre");
  pre.innerHTML = JSON.stringify(record, null, 2);
  ogProps.appendChild(pre);
}

Let's go through the code. Essentially:

  1. We import the CSS file.
  2. We get the HTML elements we are interested in.
  3. We get a handle to the service (SERVICE).
  4. We query the active tab.
  5. If we find a valid active tab, we get the URL of the tab and attempt to find the OpenGraph record for the URL. If we find the record, we render the OpenGraph image(s) and the OpenGraph properties.

One thing that is worth mentioning is that we are using the SERVICE to find the OpenGraph record for the URL. This is a simple operation that we have implemented in the previous post. However, this is an asynchronous operation and it is actually ran in the background process. This is why we are using async and await in the renderOpenGraphData function.

Wrap Up

I thought that I could learn and post about the Web browser extension development using wxt in just 3 posts. However, it took me 5 posts to cover the basics.

My take aways are:

  1. Web browser extension development is not that hard.
  2. I did not expect that cross-browser extension development would be possible. I understand that there are some limitations, but for simple extensions, it is quite straightforward.
  3. The wxt tool is a handy tool to get started with web browser extension development. Its documentation is definitely incomplete, and seems like it assumes that I knew more than I actually knew.

All in all, I am happy with the outcome. I have learned a lot given the little time I spared for this blog post series.