Skip to content

Latest commit

 

History

History
180 lines (134 loc) · 8.69 KB

ep_004.md

File metadata and controls

180 lines (134 loc) · 8.69 KB

Episode Four: Executing Scripts Service Workers

Further down the API checklist in the migration guide:

Are you currently using chrome.tabs.executeScript({code: '...'}), eval(), or new Function() in background contexts or content scripts?

Of course not, because we had to quit doing that in Firefox ages ago to get listed on addons.mozilla.org. Getting an additional script to execute in the content window when requested via chrome.runtime.sendMessage seems like a fine way to ease into background scripting, however, so let's do it. First, our baseline:

Baseline

Here's our V2 manifest, which adds a background script and requests tab permission, so we can send messages and execute a content script:

{
  "version": "0.0.104",
  "name": "Baseline",
  "description": "Manifest V2 Baseline",
  "background": {
    "scripts": ["js/background.js"]
  },
  "permissions": ["tabs", "*://*/*"],
  "content_scripts": [
    {
      "js": ["js/content.js"],
      "matches": ["*://*/*"]
    }
  ],
  "manifest_version": 2
}

Our content script writes a message to the console and asks the background for more:

console.log("Content script has loaded via Manifest V2.");

// send a message to the background requesting an additional script
chrome.runtime.sendMessage({
  cmd: "runLogic"
});

Our background script listens for messages and responds to requests for our business logic:

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  console.log("Message received!");
  console.log("request: ", request);
  console.log("sender: ", sender);
  if (request.cmd === "runLogic") {
    chrome.tabs.executeScript(sender.tab.id, {
      file: "js/logic.js"
    });
  }
});

Our business logic does nothing but let us know it's running:

console.log("A script has run from chrome.tabs.executeScript via Manifest V2.");

Drag ep_004/v2/ into chrome://extensions. Note that we now have a background view to inspect; click to do so, and then switch to the console tab. Then open a fresh tab and load a page. In the background console you should see something like this:

Message received!
request: {cmd: "runLogic"}
sender: {id: "deadbeefdeadbeefdeadbeef", [other stuff here]}

Inspect the page you just opened. In the Console tab you should see something like this:

Content script has loaded via Manifest V3. content.js
A script has run from chrome.tabs.executeScript via Manifest V2. logic.js

Manifest V3

Duplicating everything into /v3/ and changing the manifest version to 3 seems like the right first step but it will get you a gruesome pop-up error:

Failed to load extension
File ~/ep_004/v3/
Error The "background.scripts" key cannot be used with manifest_version 3. Use the "background.service_worker" key instead.
Could not load manifest.

Well, crap. Looks like we need to migrate to service workers sooner rather than later.

Changing our background property in our manifest to service_worker (being careful to make this a string and not an array!) looks like the right first thing to try:

{
  "version": "0.0.104",
  "name": "Test",
  "description": "Manifest V3 Test",
  "background": {
    "service_worker": "js/background.js"
  },
  "permissions": ["tabs"],
  "host_permissions": ["*://*/*"],
  "content_scripts": [
    {
      "js": ["js/content.js"],
      "matches": ["*://*/*"]
    }
  ],
  "manifest_version": 3
}

Everything else seems like it ought to be fine, right?

Results

  • When we load or reload the extension (by dragging /ep_004/v3 into chrome://extensions) we're not seeing an inspectable view under the extension. That's worrisome.
  • In the content tab we're seeing the message from content.js ("Content script has loaded via Manifest V3") but nothing else. It feels like the background script isn't executing?

What's Going On Here?

Per the docs:

Are you using background pages? Replace background.page or background.scripts with background.service_worker in manifest.json. Note that the service_worker field takes a string, not an array of strings.

Check.

Remove background.persistent from manifest.json.

Was never using this; check.

Update background scripts to adapt to the service worker execution context.

I feel like the docs ought to have a link here, or at least that How to Draw an Owl meme.

Fumbling Towards an Answer

Purely by accident I had a Web inspector set to the Application tab on the V2 baseline background page when I dragged in the V3 test version, and saw a service worker install itself under Service Workers from Other Origins.

  • Clicking the link to background.js gets me a new tab with the source of /js/background.js inside.
  • I also see a little red X with the number 2 inside.
  • Clicking the number -- which is linked and seems like it ought to be an error count -- does nothing.
  • There are Update and Unregister links. Update does nothing; Unregister removes background.js from the list.
  • Clicking the reload button in chrome://extensions seems to register another copy of background.js to the service worker list.
  • Clicking the reload button multiple times registers more copies of background.js as service workers; unregistering each seems to remove them.
  • When unregistering and reloading I see a brief flash of "trying to install" with a tantalizing "inspect" link.

Found Service Workers: an Introduction

  • It is not super clear to me whether I need to register my service worker or if the extension will do that for me? (Answer: no. The extension will do it for you.)
  • When I open up chrome://inspect/#service-workers I can see my script. In console I note window doesn't exist, so I suspect window.addEventListener('load') isn't going to do anything.
  • Oh, wait: chrome://inspect/#service-workers does nothing if I don't first open up chrome://serviceworker-internals and check the Open Devtools and Pause JavaScript checkbox first.
  • Workflow so far is:
    • open chrome://serviceworker-internals and check the box
    • open chrome://inspect/#service-workers
    • reload the extension
    • devtools://devtools/bundled/worker_app.html pops up with background.js in the Console tab.
    • serviceworker-internals now shows my worker with NEW installation status, STARTING running status, and this in the Log box: Console: {"lineNumber":0,"message":"The path of the provided scope ('/') is not under the max scope allowed ('/js/'). Adjust the scope, move the Service Worker script, or use the Service-Worker-Allowed HTTP header to allow the scope.","message_level":3,"sourceIdentifier":8,"sourceURL":""}

Begged for help on the Chrome developers' newsgroup, got a pointer to a service worker test that works on Manifest V2, tried it on Manifest V3, and it worked.

Stared at the error message and the example that worked until tiny drops of blood formed on my forehead and then finally got it: background.js would not install as a service worker unless it was in the root of the extension.

Lessons Learned

Background Service Workers Won't Load from Extension Subdirectories

  • Background service workers will only load from the root of the extension.
  • So js/background.js and /js/background.js won't run, while background.js will.

How To Inspect Your Background Service Worker

  • To get to an inspector, visit chrome://serviceworker-internals, reload your extension, look for your worker (should be on top) and click Inspect.
  • You'll also see console messages in the Log textarea. If you can't get your worker to start look here for clues.
  • Your worker ought to be running right after you load your extension.
  • If you've been away from the page a while and your worker isn't running, reload the page you're testing (or go interact with its context menus or other work you're doing in your extension) or click Start.
  • I've asked for a direct link to the service worker console window from the extension card in chrome://extensions.

Current Status

As long as we remember that service workers won't load from subdirectories? Seems good.