All thoughts
Reading time
1234567
minutes
PublishedRevised
/

Remote scripts in the main world with MV3 

AKA trying to do what Manifest v3 was designed to prevent us from doing.

Hero image

Turns out it is not impossible. Just annoying.

A while back, I thought to myself: what if I could automate the numerous console scripts I have written over the years? This thought developed into a small browser extension, which fetched scripts from a remote location and allowed me to select which to inject.

Everything was great! Until Manifest v3 was announced. No remote JS code, more sandboxing, and all the good stuff to prevent ad-blockers from working. But its not like I was going to give up, right.

The Task

I set out to port my extension to Mv3 around end of 2021, if memory serves me right. The two main roadblocks, as stated above, were:

  1. Fetching the scripts, hosted remotely.
  2. Injecting them so that they have access to page's localStorage/dom.

The Solution

So, how do we get around all the pesky requirements of Mv3?

[!TIP] Let the website do the heavy lifting.

That's right. Long story short, this is how the "delivery" process works:

  1. Inject our background script into the page.
  2. Inject a function from the background script, passing the needed arguments.
  3. Do the network requests from content script, running in the page.
  4. Profit.

The Code

Step 1: manifest.json

Create a new directory for your extension project and navigate into it. Inside this directory, create a file named manifest.json. This file will serve as the manifest for your extension.

manifest.json
{
    "manifest_version": 3,
    "action": {
        "default_popup": "popup.html"
    },
    "background": {
        "service_worker": "background.js"
    },
    "permissions": ["scripting"],
    "host_permissions": ["https://*/*"],
    "web_accessible_resources": [
        {
            "matches": [
                "<all_urls>"
            ],
        }
    ]
}

Step 2: background.js

Next, create a file named background.js in the same directory. This script will handle the injection of JavaScript into web pages.

background.js
function executedInPage(remoteUrl) {
	const script = document.createElement('script');
	script.src = remoteUrl;
	(document.head || document.documentElement).appendChild(script);
	script.remove();
}

const inject = async (tabId, remoteUrl) => {
	const [{ result }] =
		await new Promise((resolve) => {
			chrome.scripting.executeScript(
				{
					target: { tabId },
					world: 'MAIN',
					func: executedInPage,
					args: [remoteUrl]
				},
				resolve
			);
		});
	return result;
};

chrome.runtime.onMessage.addListener((data, _, sendResponse) => {
	(async () => {
		if (data.cmd === 'inject' && data.location && data.tabId) {
			const res = await inject(data.tabId, data.location);
			sendResponse(res);
		}
	})();

	// required to wait for sendResponse
	return true;
});

Step 3: popup.js

Now, create a file named popup.js. This is the JavaScript script you want to use in popup.html.

popup.js
// Your JavaScript code to be injected into web pages goes here
const runScript = async (
	remoteUrl: string,
	tabId: number
) => {
	const result = await chrome.runtime.sendMessage({
		cmd: 'inject',
		location: remoteUrl,
		tabId: tabId
	});
	try {
		return {
			success: result[0],
			message: result[1]
		};
	} catch {
		return {
			success: false,
			message: 'Failed converting result from script'
		};
	}
};

Step 4: popup.html

If you want to provide a user interface for your extension, create a file named popup.html. This file will contain the HTML for your extension's popup.

popup.html
<!DOCTYPE html>
<html>
<head>
  <title>Your Extension Popup</title>
  <script src="popup.js"></script>
</head>
<body>
  <h1>Welcome to Your Extension</h1>
  <button onclick="runScript(0, 'https://location.remote/script.js')">Inject JavaScript</button>
</body>
</html>

Conclusion

Congratulations! You've successfully created a Manifest V3 extension to inject JavaScript scripts into the main world of web pages. This technique opens up a wide range of possibilities for extending and customizing web browsing experiences. Experiment with different scripts and functionalities to see what works best for your needs. Happy coding!

All thoughts