Browser extension sample – Chrome/Edge – HttpRequestViewer

Browser Extensions

The Evolution of Browser Extensions: From Web Customization to Advanced Development Tools – Part 2
We discussed about The evolution of the Browser extensions in the previous post. Lets quick learn how to create a Chrome/Edge/Firefox extension. I have mentioned “Advanced development tools” in the title, but never got chance to explore those capabilities earlier. We will create a simple extension to explore the power of it.

Creating a browser extension has never been easier, thanks to the comprehensive documentation and support provided by browser vendors. Below, we’ll walk through the steps to create a simple extension for both Chrome and Microsoft Edge using Manifest V3. We will use this tool to print the list of HTTP requests that are fired in a given browser and list it in the page.

Basics of extensions:

Manifests – A manifest is a JSON file that contains metadata about a browser extension, such as its name, version, permissions, and the files it uses. It serves as the blueprint for the extension, informing the browser about the extension’s capabilities and how it should be loaded.

Key Components of a Manifest File:

Here are the key components typically found in a Manifest V3 file:

1. Manifest Version: There are different versions of the manifest file, with Manifest V3 being the latest and most widely adopted version. Manifest V3 introduces several changes aimed at improving security, privacy, and performance with lot of controversies around it. Read more about the controversies at Ghostery.
2. Name and Version: These fields define the name and version of the extension. Choose a unique name and version. An excellent guide of version semantics is available here.
3. Description: A short description of the extension’s functionality.
4. Action: Defines the default popup and icon for the browser action (e.g., toolbar button).
5. Background: Specifies the background script that runs in the background and can handle events like network requests and alarms.
6. Content Scripts: Defines scripts and stylesheets to be injected into matching web pages.
7. Permissions: Lists the permissions the extension needs to operate, such as access to tabs, storage, and specific websites.
8. Icons: Specifies the icons for the extension in different sizes. For this post I created a simple icon using Microsoft Designer. I gave a simple prompt with the description above and I got the below image. Extension requires different sizes for showing it in different places. I used Chrome Extension Icon Generator and generated different sizes as needed.

     

9. Web Accessible Resources: Defines which resources can be accessed by web pages.

Create a project structure as follows:

HttpRequestViewer/
|-- manifest.json
|-- popup.html
|-- popup.js
|-- background.js
|-- history.html
|-- history.js
|-- popup.css
|-- styles.css
|-- icons/
    |-- icon.png
    |-- icon16.png
    |-- icon32.png
    |-- icon48.png
    |-- icon128.png

Manifest.json

{
  "name": "API Request Recorder",
  "description": "Extension to record all the HTTP request from a webpage.",
  "version": "0.0.1",
  "manifest_version": 3,
  "host_permissions": [""],
  "permissions": ["activeTab", "webRequest", "storage"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icons/icon.png"
  },
  "background": {
    "service_worker": "background.js"
  },
  "icons": {
    "16": "icons/icon16.png",
    "32": "icons/icon32.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self';"
  },
  "web_accessible_resources": [{ "resources": ["images/*.png"], "matches": ["https://*/*"] }]
}

popup.html
We have two options with the extension.

1. A button with record option to start recording all the HTTP requests
2. Link to view the history of HTTP Requests recorded

<!DOCTYPE html>
<html>
  <head>
    <title>API Request Recorder</title>

    <link rel="stylesheet" href="popup.css" />
  </head>
  <body>
    <div class="heading">
      <img class="logo" src="icons/icon48.png" />
      <h1>API Request Recorder</h1>
    </div>
    <button id="startStopRecord">Record</button>

    <div class="button-group">
      <a href="#" id="history">View Requests</a>
    </div>

    <script src="popup.js"></script>
  </body>
</html>

popup.js
Two event listeners are registered for recording (with start / stop) and viewing history.
First event is used to send a message to the background.js, while the second one instructs chrome to open the history page in new tab.

document.getElementById("startStopRecord").addEventListener("click", () => {
  chrome.runtime.sendMessage({ action: "startStopRecord" });
});

document.getElementById("history").addEventListener("click", () => {
  chrome.tabs.create({ url: chrome.runtime.getURL("/history.html") });
});

history.html

 
<!DOCTYPE html>
<html>
  <head>
    <title>History</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <h1>History Page</h1>
    <table>
      <thead>
        <tr>
		  <th>Method</th>
          <th>URL</th>
          <th>Body</th>
        </tr>
      </thead>
      <tbody id="recorded-data-body">
        <!-- Data will be populated here -->
      </tbody>
    </table>
    <script src="history.js"></script>
  </body>
</html>

history.js
Requests background.js to “getRecordedData” and renders the result in the html format.

document.addEventListener("DOMContentLoaded", () => {
  chrome.runtime.sendMessage({ action: "getRecordedData" }, (response) => {
    const tableBody = document.getElementById("recorded-data-body");
    response.forEach((record) => {
      const row = document.createElement("tr");
      const urlCell = document.createElement("td");
      const methodCell = document.createElement("td");
      const bodyCell = document.createElement("td");

      urlCell.textContent = record.url;
      methodCell.textContent = record.method;
      bodyCell.textContent = record.body;

      row.appendChild(methodCell);
      row.appendChild(urlCell);
      row.appendChild(bodyCell);
      tableBody.appendChild(row);
    });
  });
});

background.js
Background JS works as a service worker for this extension, listening and handling events.
The background script does not have access to directly manipulate the user page content, but can post results back for the popup/history script to handle the cosmetic changes.

let isRecording = false;
let recordedDataList = [];

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  console.log("Obtined message: ", message);
  if (message.action === "startStopRecord") {
    if (isRecording) {
      isRecording = false;
      console.log("Recording stopped...");
      sendResponse({ recorder: { status: "stopped" } });
    } else {
      isRecording = true;
      console.log("Recording started...");
      sendResponse({ recorder: { status: "started" } });
    }
  } else if (message.action === "getRecordedData") {
    sendResponse(recordedDataList);
  } else {
    console.log("Unhandled action ...");
  }
});

chrome.webRequest.onBeforeRequest.addListener(
  (details) => {
    if (isRecording) {
      let requestBody = "";
      if (details.requestBody) {
        if (details.requestBody.formData) {
          requestBody = JSON.stringify(details.requestBody.formData);
        } else if (details.requestBody.raw) {
          requestBody = new TextDecoder().decode(new Uint8Array(details.requestBody.raw[0].bytes));
        }
      }
      recordedDataList.push({
        url: details.url,
        method: details.method,
        body: requestBody,
      });
      console.log("Recorded Request:", {
        url: details.url,
        method: details.method,
        body: requestBody,
      });
    }
  },
  { urls: [""] },
  ["requestBody"]
);

Lets load the Extension

All set, now lets load the extension and test it.

  • Open Chrome/Edge and go to chrome://extensions/ or edge://extensions/ based on your browser.
  • Enable “Developer mode” using the toggle in the top right corner.
  • Click “Load unpacked” and select the directory of your extension.

Load extensionupload extension

  • Your extension should now be loaded, and you can interact with it using the popup.
  • When you click the “Record” button, it will start logging API requests to the console.

  • Click the “Record” button again and hit the “View requests” link in the popup to view the history of APIs.

I have a sample page (https://itechgenie.com/demos/apitesting/index.html) with 4 API calls, which also loads images based on the API responses. You could see all the API requests that is fired from the page including the JS, CSS, Images and API calls.


Now its up to the developers imagination to build the extension to handle these APIs request and response data and give different experience.

Code is available in GitHub at HttpRequestViewer