google-chrome-extensionbrowser-extensionmicrosoft-edge-extension

Chrome.scripting does nothing and ends the script


Im currently doing the "Inject scripts into the active tab" part of Getting Started, in the Chrome Extension tutorial, where I've been spending a little time troubleshooting why the page never changes to a more simple layout, even when nearly copy-pasting the entire extension folder.

The extension action badge text switches between "ON" and "OFF" when clicked, but thats about it.

Manifest.json

{
    "manifest_version": 3,
    "name": "Focus Mode",
    "description": "Enables focus mode on Chrome's official Extensions and Chrome Web Store documentation",
    "version": "1.0",
    "icons": {
        "16": "images/icon-16.png",
        "32": "images/icon-32.png",
        "48": "images/icon-48.png",
        "128": "images/icon-128.png"
    },
    "background": {
        "service_worker": "background.js"
    },
    "action": {
        "default_icon": {
            "16": "images/icon-16.png",
            "32": "images/icon-32.png",
            "58": "images/icon-48.png",
            "128": "images/icon-128.png"
        }
    },
    "permissions": [
        "activeTab",
        "scripting"
    ]
}

background.js

chrome.runtime.onInstalled.addListener(() => {
    chrome.action.setBadgeText({
        text: "OFF"
    });
});

// both URL's form tutorial merged into root URL
const chromeDocs = "https://developer.chrome.com/docs/";

chrome.action.onClicked.addListener(async (tab) => {
    if (tab.url.startsWith(chromeDocs)) {
        // retrieve action badge to check if extension is on or off
        const prevState = await chrome.action.getBadgeText({ tabId: tab.id});
        // next state will be opposite
        const nextState = (prevState === "ON") ? "OFF" : "ON";

        // set action badge to next state
        await chrome.action.setBadgeText({
            tabId: tab.id,
            text: nextState,
        });

        if (nextState === "ON") {
          // Insert the CSS file when the user turns the extension on
          chrome.scripting.insertCSS({
            css: "FocusMode.css",
            target: { tabId: tab.id },
          });
        } else if (nextState === "OFF") {
          // Remove the CSS file when the user turns the extension off
          chrome.scripting.removeCSS({
            css: "FocusMode.css",
            target: { tabId: tab.id },
          });
        }
      }
    });

FocusMode.css

* {
  display: none !important;
}

html,
body,
*:has(article),
article,
article * {
  display: revert !important;
}

[role='navigation'] {
  display: none !important;
}

article {
  margin: auto;
  max-width: 700px;
}

Because its a little hassle to get alert() and console.log() to do anything with extensions, I found a post that simply uses chrome.windows.create() to mark how far the code gets. Found out this way that everything down to the CSS inserting is executed, which got confirmed when temporarily changing up the CSS to something way simpler;

p {
  background-color: blue;
}

I don't really know how the complicated CSS works, but its very clear if this one gets inserted - which somehow it didn't. The chrome extension tutorial remains untouched despite Inspect Element saying the text that should be changed, is infact paragraph elements.

Lastly tried just using chrome.scripting.executeScript() and literally yoink an example right from the docs, and have the injected script be nothing but chrome.windows.create() which by itself worked before, but this or including .create() afterwards doesnt do anything. Through this I learnt that its simply chrome.scripting that does nothing and halts the executing.

All testing was done in the "Inject scripts into the active tab" tutorial page, so thats not the problem. What could be a possible cause?


Solution

  • Chrome is bugged: it forgets activeTab in asynchronous code i.e. after await.

    A [better] solution would be to store the state in the tab as it allows to set the mode per each tab. Your CSS will be injected just once and it'll depend on a class name of the <html> element that you will toggle via document.documentElement:

    chrome.action.onClicked.addListener(async (tab) => {
      if (tab.url.startsWith(chromeDocs)) {
        const tabId = tab.id;
        chrome.scripting.insertCSS({
          target: {tabId},
          files: ['FocusMode.css'],
        }).catch(() => {});
        const [{result}] = await chrome.scripting.executeScript({
          target: {tabId},
          func: () => document.documentElement.classList.toggle('FocusMode'),
        }).catch(() => [{}]);
        if (result != null) { // skip inaccessible tabs that cannot be injected
          chrome.action.setBadgeText({
            tabId,
            text: result ? 'ON' : 'OFF',
          });
        }
      }
    });
    

    Here's the CSS:

    .FocusMode:root {
      * {
        display: none !important;
      }
      &,
      body,
      *:has(article),
      article,
      article * {
        display: revert !important;
      }
      [role='navigation'] {
        display: none !important;
      }
      article {
        margin: auto;
        max-width: 700px;
      }
    }
    

    If you want your extension to run in older browsers you can either compile this CSS in postcss with preset-env or manually alter each selector:

    .FocusMode:root * {
      display: none !important;
    }
    .FocusMode:root,
    .FocusMode:root body,
    .FocusMode:root *:has(article),
    .FocusMode:root article,
    .FocusMode:root article * {
      display: revert !important;
    }
    .FocusMode:root [role='navigation'] {
      display: none !important;
    }
    .FocusMode:root article {
      margin: auto;
      max-width: 700px;
    }
    

    P.S. Use devtools for the background script.