Using `chrome.tabs.onActivated` for auto update state

Using chrome.tabs.onActivated for auto update state

author

Rajat Dhoot

28 May 2024 - 05 Mins read

Using chrome.tabs.onActivated to Update Data in Content Script from Options Script

In many Chrome extensions, you might need to update the content script with data changed in the options script. For example, imagine you are building an extension that allows users to configure settings in the options page, and these settings need to be reflected immediately in the content script of the active tab. The chrome.tabs.onActivated listener can help you achieve this.

The Use Case

Suppose you have an options script where users can change the configuration of your extension. Once the settings are updated, you want the content script to fetch the new settings whenever the user switches to a different tab. This ensures that the content script always has the latest data without requiring a manual refresh.

Step-by-Step Implementation

1. Background Script

First, the background script will listen for tab activation events and send a message to the active tab's content script to fetch the updated data.

// Adds an event listener that triggers when a tab is activated (focused).
chrome.tabs.onActivated.addListener(function (info) {
  // Queries the active tab within the current window.
  chrome.tabs.query({ active: true }, function (tabs) {
    // Sends a message to the active tab to perform an action (e.g., updating the tab).
    chrome.tabs.sendMessage(tabs[0].id, { action: "SETTINGS_UPDATED" });
  });
});

2. Options Script

In the options script, you save the updated settings in Chrome's storage. When the settings are changed, a message can also be sent to the background script to trigger an update.

Here's an example of how you can implement the options script using React:

import useStore from "@root/src/hooks/useStore";
import "@pages/options/Options.css";
import { setStorageData } from "@root/src/utils";
 
const Options = () => {
  const [state, dispatch] = useStore();
 
  const saveSettings = async () => {
    await setStorageData(state);
    chrome.runtime.sendMessage({ action: "SETTINGS_UPDATED", payload: state });
  };
 
  return (
    <div className="min-h-screen  justify-between items-center flex">
      <div className="flex flex-col m-auto w-1/2 space-y-4">
        <h1 className="text-5xl">Options</h1>
        <input
          type="text"
          className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
          value={state.name}
          onChange={(e) =>
            dispatch({
              type: "UPDATE_SETTINGS",
              payload: { name: e.target.value },
            })
          }
        />
        <button
          className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
          onClick={saveSettings}
        >
          Save
        </button>
        <p className="text-sm">
          Changes done here get reflected to content script without
          automatically
        </p>
      </div>
    </div>
  );
};
 
export default Options;

3. Content Script

In the content script, use React hooks to manage state and listen for messages from the background script. When the SETTINGS_UPDATED message is received, fetch the updated settings.

import logo from "@assets/img/logo.png";
import useStore from "@root/src/hooks/useStore";
 
export default function App() {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const [state, dispatch] = useStore();
 
  return (
    <div className="text-black border-2 p-2">
      <div className="bg-indigo-200 h-full w-full flex justify-center items-center">
        <div className="text-center">
          <div className="flex justify-center">
            <img
              src={chrome.runtime.getURL(logo)}
              alt="Launchify"
              className="h-12 w-12"
            />
          </div>
          <h1 className="text-3xl">Welcome to {state.name}</h1>
          <h1 className="text-xl">Let&apos;s get started</h1>
        </div>
      </div>
    </div>
  );
}

4. App State

import { useEffect, useReducer } from "react";
import { getStorageData } from "@root/src/utils";
// Reducer function to handle state changes
function reducer(state, action) {
  switch (action.type) {
    // Action to set the initial state
    case "UPDATE_SETTINGS":
      return {
        ...state,
        ...action.payload,
      };
    // Default case returns the current state
    default:
      return state;
  }
}
 
// Custom hook to manage state using useReducer and useEffect
const useStore = () => {
  // Initialize the state and dispatch function using useReducer
  const [state, dispatch] = useReducer(reducer, {
    name: "Launchify",
  });
 
  // useEffect hook to handle side effects
  useEffect(() => {
    // Add a message listener to listen for messages from the background script or other parts of the extension
    chrome.runtime.onMessage.addListener(async (message) => {
      switch (message.action) {
        // Handle the 'SETTINGS_UPDATED' action sent from the background script
        case "SETTINGS_UPDATED": {
          // Currently does nothing, but can be extended with further logic to update the component state or perform other actions
          const response = await getStorageData(Object.keys(state));
          dispatch({ type: "UPDATE_SETTINGS", payload: response });
          return true;
        }
      }
      return true; // Indicates asynchronous response
    });
 
    // Cleanup function to remove the message listener when the component is unmounted or dependencies change
    return () => {
      chrome.runtime.onMessage.removeListener(() => {});
    };
  }, [dispatch, state]); // Dependencies array for useEffect, re-runs the effect when dispatch or state changes
 
  // Return the current state and dispatch function from the hook
  return [state, dispatch];
};
 
export default useStore;
 
/*
 * This code defines a custom hook, `useStore`, which manages state using `useReducer` and listens for messages from the Chrome extension runtime.
 *
 * 1. The `reducer` function handles state changes based on action types.
 *    - The 'SET_INIT_STATE' action sets the initial state with the name 'Launchify'.
 *    - The default case returns the current state if the action type is not recognized.
 *
 * 2. The `useStore` hook initializes state using `useReducer` with the `reducer` function and initial state.
 *    - `state` represents the current state.
 *    - `dispatch` is a function to dispatch actions to update the state.
 *
 * 3. The `useEffect` hook is used to add and remove a message listener.
 *    - `chrome.runtime.onMessage.addListener` listens for messages sent from other parts of the extension.
 *    - The listener checks the action type of the received message.
 *    - If the action type is 'SETTINGS_UPDATED', it currently does nothing but can be extended with further logic.
 *      - This is where you would handle the 'SETTINGS_UPDATED' action sent from the background script to update the component state or perform other actions as needed.
 *      - For example, you might update the state with new data related to the active tab.
 *    - The cleanup function returned by `useEffect` removes the message listener to prevent memory leaks.
 *    - The dependencies array `[dispatch, state]` ensures the effect runs when these dependencies change.
 *
 * 4. The `useStore` hook returns the current state and the dispatch function for use in React components.
 *
 * 5. `export default useStore` makes the custom hook available for import in other parts of the application.
 */

Explanation

  1. Background Script:

    • chrome.tabs.onActivated.addListener: Adds an event listener that triggers when a tab is activated.
    • chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { ... }): Queries the active tab in the current window.
    • chrome.tabs.sendMessage(tabs[0].id, { action: 'SETTINGS_UPDATED' });: Sends a message to the active tab's content script.
  2. Options Script:

    • React Component: Uses React hooks to manage settings state.
    • chrome.storage.sync.set: Saves the updated settings.
    • chrome.runtime.sendMessage({ action: 'SETTINGS_UPDATED' });: Sends a message to notify that settings have been updated.
  3. Content Script:

    • Reducer Function: Manages state changes based on action types.
    • useStore Hook:
      • useReducer: Manages the state with the reducer.
      • useEffect: Listens for messages and updates settings when SETTINGS_UPDATED action is received.
      • chrome.runtime.onMessage.addListener: Listens for messages from the background script.
      • chrome.storage.sync.get: Fetches updated settings from Chrome's storage.

Conclusion

Using the chrome.tabs.onActivated listener, you can effectively update the content script with the latest data whenever the user switches to a different tab. This approach ensures that your content script always has the most current data, providing a seamless user experience. The provided code example demonstrates how to implement this functionality in a Chrome extension.

To take your Chrome extension development to the next level, consider using Launchify. Launchify provides a comprehensive set of tools and resources to streamline your extension development process, making it easier to build, test, and deploy your Chrome extensions. Don't miss out on this opportunity to enhance your development workflow and create high-quality extensions. Purchase Launchify today and elevate your extension projects to new heights!

Recent Articles

Exploring the Best Headless CMS for Next.js Projects

Exploring the Best Headless CMS for Next.js Projects

### Exploring the Best Headless CMS for Next.js Projects The integration of a headless CMS (Content Management System) into Next.js projects has been a hot topic among developers. With the rise of J...

author

Rajat Dhoot

31 May 2024 - 04 Mins read

Rapid Web Development with Next.js Boilerplates

Rapid Web Development with Next.js Boilerplates

# Top 5 Next.js Boilerplates for Rapid Web Development In the fast-paced world of web development, efficiency and speed are paramount. This is where boilerplates come in, offering pre-configured set...

author

Khush Mahajan

30 May 2024 - 02 Mins read

Get KwikTwik Now

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi egestas
Werat viverra id et aliquet. vulputate egestas sollicitudin.

Download The Theme
bg wave