Giter VIP home page Giter VIP logo

webext-dynamic-content-scripts's Introduction

webext-dynamic-content-scripts npm version

WebExtension module: Automatically registers your content_scripts on domains added via permissions.request

  • Browsers: Chrome, Firefox, and Safari
  • Manifest: v2 and v3

This module will automatically register your content_scripts from manifest.json into new domains granted via permissions.request(), or via webext-domain-permission-toggle.

The main use case is ship your extension with a minimal set of hosts and then allow the user to enable it on any domain; this way you don't need to use a broad <all_urls> permission.

Guides

How to let your users enable your extension on any domain.

Install

You can download the standalone bundle and include it in your manifest.json. Or use npm:

npm install webext-dynamic-content-scripts
// This module is only offered as a ES Module
import 'webext-dynamic-content-scripts';

Usage

For Manifest v2, refer to the usage-mv2 documentation.

You need to:

  • import webext-dynamic-content-scripts in the worker (no functions need to be called)
  • specify optional_host_permissions in the manifest to allow new permissions to be added
  • specify at least one content_scripts
// example background.worker.js
navigator.importScripts('webext-dynamic-content-scripts.js');
// example manifest.json
{
	"permissions": ["scripting"],
	"optional_host_permissions": ["*://*/*"],
	"background": {
		"service_worker": "background.worker.js"
	},
	"content_scripts": [
		{
			"matches": ["https://github.com/*"],
			"css": ["content.css"],
			"js": ["content.js"]
		}
	]
}

activeTab tracking

By default, the module will only inject the content scripts into newly-permitted hosts, but it will ignore temporary permissions like activeTab. If you also want to automatically inject the content scripts into every frame of tabs as soon as they receive the activeTab permission, import a different entry point instead of the default one.

import 'webext-dynamic-content-scripts/including-active-tab.js';

Note This does not work well in Firefox because of some compounding bugs:

  • activeTab seems to be lost after a reload
  • further contextMenu clicks receive a moz-extension URL rather than the current page’s URL

Additional APIs

isContentScriptRegistered(url)

You can detect whether a specific URL will receive the content scripts by importing the utils file:

import {isContentScriptRegistered} from 'webext-dynamic-content-scripts/utils.js';

if (await isContentScriptRegistered('https://google.com/search')) {
	console.log('Either way, the content scripts are registered');
}

isContentScriptRegistered returns a promise that resolves with a string indicating the type of injection ('static' or 'dynamic') or false if it won't be injected on the specified URL.

Related

Permissions

Others

  • webext-options-sync - Helps you manage and autosave your extension's options. Chrome and Firefox.
  • webext-storage-cache - Map-like promised cache storage with expiration. Chrome and Firefox
  • webext-detect-page - Detects where the current browser extension code is being run. Chrome and Firefox.
  • web-ext-submit - Wrapper around Mozilla’s web-ext to submit extensions to AMO.
  • Awesome-WebExtensions - A curated list of awesome resources for WebExtensions development.

License

MIT © Federico Brigante

webext-dynamic-content-scripts's People

Contributors

andersdjohnson avatar aspiers avatar bfred-it avatar darkred avatar fregante avatar nickytonline avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

webext-dynamic-content-scripts's Issues

Add unit testing

Useful for:

That new code is barely tested and would benefit from testing outside the puppeteer (also because it's impossible to test activeTab)

Add `isContentScriptRegistered`

From: https://github.com/pixiebrix/pixiebrix-extension/blob/6ff0e7647fe02e53fb9d3a35f200ed5afd1a3fd2/src/background/util.ts#L29-L44

/** Checks whether a URL will have the content scripts automatically injected */
export async function isContentScriptRegistered(url: string): Promise<boolean> {
  // Injected by the browser
  const manifestScriptsOrigins = browser.runtime
    .getManifest()
    .content_scripts.flatMap((script) => script.matches);

  // Injected by `webext-dynamic-content-scripts`
  const { origins } = await getAdditionalPermissions({
    strictOrigins: false,
  });

  // Do not replace the 2 calls above with `permissions.getAll` because it might also
  // include hosts that are permitted by the manifest but have no content script registered.
  return patternToRegex(...origins, ...manifestScriptsOrigins).test(url);
}

`all_frames` isn't supported in Chrome

Thanks for putting this together!

This repo uses https://github.com/fregante/content-scripts-register-polyfill for polyfiling browser.contentScripts.register() in Chrome

Just wanted to link to the bug in the other repo, as I hit this problem and it took some sleuthing to find it: fregante/content-scripts-register-polyfill#2

I'm seeing that it actually works 20-25% of the time when the parent tabs URL is an allowed origin. So there must be a race condition in page loading events. (I'm using run_at document_idle, but also tried with document_end)

Build error: Property 'contentScripts' does not exist on type 'typeof chrome'

I'm seeing this error when I try to build the polyfill:

webext-dynamic-content-scripts git:master ❯ npm run build

> [email protected] build code/webext-dynamic-content-scripts
> tsc

index.ts:23:36 - error TS2339: Property 'contentScripts' does not exist on type 'typeof chrome'.

23              const registeredScript = chrome.contentScripts.register({
                                                ~~~~~~~~~~~~~~


Found 1 error.

Any ideas why that's happening?

Automatically listen to activetab (optionally?)

In some cases (most?) you'd also want the content script to be automatically available on the activeTab as well. This would avoid conflicting scripts trying to inject the script via different code paths:

ActiveTab-tracking code can be found here and will likely be extracted, maybe into webext-tools:

On top of that, I think it also needs to listen to tabs and manually inject the scripts instead of relying on registerContentScripts. However it seems that the API supports activeTab in MV3: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/registerContentScripts


To preserve the side-effects-only nature of the module, I think it can export an additional entry point:

import 'webext-dynamic-content-scripts/including-activetab';

v10 doesn't fully support complex conditions in MV2 Chrome

There's a very rare situation that is no longer covered by webext-dynamic-content-scripts. This is a long list of conditions that have to appear at the same time for the bug to appear:

  • You're on Chrome
  • Your manifest_version is 2
  • Your manifest has multiple objects inside content_scripts
  • Your content_scripts mentions the same file twice (for example to be injected onto multiple domains, via manifest)
  • Your content_scripts have one of run_at, exclude_matches, all_frames
  • Your other content_scripts do not have the same exact property

If this is happening, you should stay on v9 of this module until you upgrade to Manifest v3.

Here's an example extension affected by this limitation:

{
	"manifest_version": 2,
	"content_scripts": [
		{
			"all_frames": true, // Present here, missing from the next one
			"matches": ["https://example.com/*"],
			"js": ["content.js"],
		},
		{
			"matches": ["https://www.example.org/*"],
			"js": ["content.js"],
		}
	]
}

Exclude URLs that already overlap with defaults (on first launch too)

Extension of:

The above issue was fixed with:

excludeMatches: config.matches,

But it didn't cover injectToExistingTabs:

chrome.tabs.query({
url: origins,
}, tabs => {
for (const tab of tabs) {
if (tab.id) {
void injectContentScript(tab.id, scripts);

This is particularly visible when all_urls is granted, so it always covers all tabs.

Update puppeteer to v21+ for the tests

Attempted and failed in #63

Example of failure:


  ● static: excludeMatches › should load the content script only in iframe, once

    [object Object] is not supported

      89 | 	it('should load the content script only in iframe, once', async () => {
      90 | 		await expectToNotMatchElement(page, '.web-ext');
    > 91 | 		await expect(iframeOfExcludedParent).toMatchElement('.web-ext');
         | 		      ^
      92 | 		await expectToNotMatchElement(iframeOfExcludedParent, '.web-ext + .web-ext');
      93 | 	});
      94 | });

      at expectPuppeteerInstance (node_modules/expect-puppeteer/dist/index.js:488:11)
      at expectPuppeteer (node_modules/expect-puppeteer/dist/index.js:492:26)
      at Object.expect (test/browser.js:91:9)

  ● dynamic: excludeMatches › should load page and iframe

    [object Object] is not supported

      84 | 	it('should load page and iframe', async () => {
      85 | 		await expect(page).toMatchElement('title', {text: 'Excluded page'});
    > 86 | 		await expect(iframeOfExcludedParent).toMatchElement('title', {text: 'Framed page'});
         | 		      ^
      87 | 	});
      88 |
      89 | 	it('should load the content script only in iframe, once', async () => {

      at expectPuppeteerInstance (node_modules/expect-puppeteer/dist/index.js:488:11)
      at expectPuppeteer (node_modules/expect-puppeteer/dist/index.js:492:26)
      at Object.expect (test/browser.js:86:9)

  ● dynamic: excludeMatches › should load the content script only in iframe, once

    [object Object] is not supported

      89 | 	it('should load the content script only in iframe, once', async () => {
      90 | 		await expectToNotMatchElement(page, '.web-ext');
    > 91 | 		await expect(iframeOfExcludedParent).toMatchElement('.web-ext');
         | 		      ^
      92 | 		await expectToNotMatchElement(iframeOfExcludedParent, '.web-ext + .web-ext');
      93 | 	});
      94 | });

      at expectPuppeteerInstance (node_modules/expect-puppeteer/dist/index.js:488:11)
      at expectPuppeteer (node_modules/expect-puppeteer/dist/index.js:492:26)
      at Object.expect (test/browser.js:91:9)
Test Suites: 1 failed, 1 total
Tests:       10 failed, 6 passed, 16 total

Manifest v3 compatibility

Manifest v3 might have brought some changes that broke this script. I never tested it, but it should be.

Allow injecting a provided list of scripts

Instead of just injecting scripts existing in manifest.json, inject a list of styles and scripts using the same format. This is already possible natively, example:

chrome.tabs.insertCSS(tabId, { file: "vendor/humane-ghosttext.css" });
chrome.tabs.executeScript(tabId, { file: "vendor/humane-ghosttext.min.js" });
chrome.tabs.executeScript(tabId, { file: "scripts/input-area.js" });
chrome.tabs.executeScript(tabId, { file: "scripts/content.js" }, callback);

That would become:

injectContentScripts(tabId, {
  run_at: "document_start",
  css: ["vendor/humane-ghosttext.css"],
  js: [
    "vendor/humane-ghosttext.min.js",
    "scripts/input-area.js",
    "scripts/content.js"
  ]
});

Worth it? It would be nice to provide a common interface.

v.8.0.0 causes build error

This might be related to my build setup, but it was working fine on 7.1.2 so I am creating an issue anyway.

ERROR in ./node_modules/content-scripts-register-polyfill/node_modules/webext-polyfill-kinda/index.js 22:34
Module parse failed: Unexpected token (22:34)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| }
| 
> const chromeP = globalThis.window?.chrome && new NestedProxy(window.chrome);
| 
| export default chromeP;

import from 'webext-dynamic-content-scripts/utils.js' doesn't work

The README says that this should work:

import {isContentScriptRegistered} from 'webext-dynamic-content-scripts/utils.js';

However, it refuses to import:

[commonjs--resolver] Missing "./utils.js" export in "webext-dynamic-content-scripts" package

As far as I can tell, this is due to the "exports" section of package.json not covering this file:

"exports": {
".": {
"types": "./distribution/index.d.ts",
"default": "./distribution/index.js"
},
"./including-active-tab.js": {
"types": "./distribution/including-active-tab.d.ts",
"default": "./distribution/including-active-tab.js"
}
},

I tried adding these lines to this section:

		"./utils.js": {
			"types": "./distribution/utils.d.ts",
			"default": "./distribution/utils.js"
		}

and then the import immediately worked.

I'm no Javascript expert, but it seems a bit odd to be declaring each of these files manually in "exports". Can't it be done with a wildcard? And is there any reason not to re-export isContentScriptRegistered from index.ts, so that it can be imported directly from 'webext-dynamic-content-scripts'?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.