{"id":20797186,"url":"https://github.com/daniguardiola/content-script-activation","last_synced_at":"2025-04-09T19:43:12.596Z","repository":{"id":213188304,"uuid":"733290286","full_name":"DaniGuardiola/content-script-activation","owner":"DaniGuardiola","description":"Simpler injection of content scripts in browser extensions. Inject once, activate on click.","archived":false,"fork":false,"pushed_at":"2023-12-22T21:32:54.000Z","size":101,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-23T21:45:48.536Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DaniGuardiola.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-12-19T02:01:11.000Z","updated_at":"2023-12-28T02:39:12.000Z","dependencies_parsed_at":"2023-12-22T22:26:34.728Z","dependency_job_id":null,"html_url":"https://github.com/DaniGuardiola/content-script-activation","commit_stats":null,"previous_names":["daniguardiola/content-script-activation"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DaniGuardiola%2Fcontent-script-activation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DaniGuardiola%2Fcontent-script-activation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DaniGuardiola%2Fcontent-script-activation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DaniGuardiola%2Fcontent-script-activation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DaniGuardiola","download_url":"https://codeload.github.com/DaniGuardiola/content-script-activation/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248101407,"owners_count":21047966,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-17T16:32:53.773Z","updated_at":"2025-04-09T19:43:12.575Z","avatar_url":"https://github.com/DaniGuardiola.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1\u003econtent-script-activation\u003c/h1\u003e\n\n[![API reference](https://img.shields.io/badge/tsdocs-%23007EC6?style=flat\u0026logo=typescript\u0026logoColor=%23fff\u0026label=API%20reference\u0026labelColor=%23555555)](https://tsdocs.dev/docs/content-script-activation/) [![Bundle size](https://deno.bundlejs.com/?q=content-script-activation%40latest\u0026badge=\u0026badge-style=flat\u0026badge-raster=false)](https://bundlejs.com/?q=content-script-activation%40latest)\n\nSimpler injection of content scripts in browser extensions. Inject once, activate on click.\n\n```bash\nnpm i content-script-activation\n```\n\n\u003c!-- vscode-markdown-toc --\u003e\n\n- [What this does](#what-this-does)\n- [Usage](#usage)\n- [Documentation](#documentation)\n  - [Filtering tabs](#filtering-tabs)\n  - [Executing code before or after injection](#executing-code-before-or-after-injection)\n  - [Injecting styles](#injecting-styles)\n  - [Customizing script and stylesheet injection](#customizing-script-and-stylesheet-injection)\n  - [Injecting multiple scripts and stylesheets](#injecting-multiple-scripts-and-stylesheets)\n  - [Scripts shorthands](#scripts-shorthands)\n  - [Omitting the activation callback](#omitting-the-activation-callback)\n  - [Multiple instances](#multiple-instances)\n  - [Manual activation](#manual-activation)\n- [Browser support](#browser-support)\n- [Features under consideration](#features-under-consideration)\n\n\u003c!-- vscode-markdown-toc-config\n\tnumbering=false\n\tautoSave=true\n\t/vscode-markdown-toc-config --\u003e\n\u003c!-- /vscode-markdown-toc --\u003e\n\n## \u003ca name='Whatthisdoes'\u003e\u003c/a\u003eWhat this does\n\nWhen building a browser extension, it is a common pattern to inject a content script when the extension icon is clicked. This is usually done like this:\n\n```ts\nbrowser.action.onClicked.addListener((tab) =\u003e {\n  browser.scripting.executeScript({\n    target: { tabId: tab.id },\n    files: [\"content-script.js\"],\n  });\n});\n```\n\nHowever, the problem is that on every click, the content script is injected again. This can cause trouble depending on how the content script is written. For example, if the content script adds an event listener to the `window` object, it will be added again on every click, leading to unexpected behavior.\n\nThis package does things differently:\n\n- The content script is injected only once on the first click.\n- The \"activation\" event is triggered on every click, including the first one.\n\nTo illustrate this, consider the following sequence of events:\n\n```\nExtension icon clicked\n  Content script injected\n  Activation event triggered\nExtension icon clicked\n  Activation event triggered\nExtension icon clicked\n  Activation event triggered\n(...)\n```\n\nThis model is simpler and lets you think about \"activation\" as a single event that happens on every click. Script injection is handled for you.\n\n## \u003ca name='Usage'\u003e\u003c/a\u003eUsage\n\nOn the service worker:\n\n```ts\nimport { setupContentScriptActivation } from \"content-script-activation\";\n\nsetupContentScriptActivation(\"content-script.js\");\n```\n\nOn the content script:\n\n```ts\nimport { setupActivation } from \"content-script-activation\";\n\nsetupActivation(() =\u003e {\n  // do something on activation\n});\n```\n\n## \u003ca name='Documentation'\u003e\u003c/a\u003eDocumentation\n\n### \u003ca name='Filteringtabs'\u003e\u003c/a\u003eFiltering tabs\n\nIf you want to inject the content script only on certain tabs, you can pass a filter function to `setupContentScriptActivation`:\n\n```ts\nsetupContentScriptActivation({\n  filterTab: (tab) =\u003e tab.url?.startsWith(\"http\"),\n  inject: \"content-script.js\",\n});\n```\n\nThe `tab` object ([`tabs.Tab` type](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/Tab)) is the one passed to the [`browser.action.onClicked.addListener`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/action/onClicked) callback, and contains information about the tab where the extension icon was clicked (such as the ID, URL, title, etc).\n\n### \u003ca name='Executingcodebeforeorafterinjection'\u003e\u003c/a\u003eExecuting code before or after injection\n\nIf you need to run some code before injection (e.g. preparing a database connection) or after injection (e.g. sending a message to the content script), you can use the `beforeInject` and `afterInject` options:\n\n```ts\nsetupContentScriptActivation({\n  inject: {\n    async beforeInjection(context) {\n      // ...\n    },\n    async afterInjection(context) {\n      // ...\n    },\n    scripts: \"content-script.js\",\n  },\n});\n```\n\nBoth functions can be synchronous or asynchronous. They receive a `context` object with information about the tab where the content script is injected.\n\n### \u003ca name='Injectingstyles'\u003e\u003c/a\u003eInjecting styles\n\nYou can inject stylesheets in a similar way to scripts:\n\n```ts\nsetupContentScriptActivation({\n  inject: {\n    // ...\n    styles: \"content-style.css\",\n  },\n});\n```\n\n### \u003ca name='Customizingscriptandstylesheetinjection'\u003e\u003c/a\u003eCustomizing script and stylesheet injection\n\nIf you need more control over how scripts or stylesheets are injected, you can pass option objects instead of strings:\n\n```ts\nsetupContentScriptActivation({\n  inject: {\n    scripts: {\n      files: [\"content-script.js\"],\n      injectImmediately: false,\n    },\n    styles: {\n      files: [\"content-style.css\"],\n      origin: \"USER\",\n    },\n  },\n});\n```\n\nThe options that can be passed correspond to the options that can be passed to [`browser.scripting.executeScript`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/executeScript) and [`browser.scripting.insertCSS`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/insertCSS), except for the `target` option (which is always set to the tab where the extension icon was clicked).\n\n### \u003ca name='Injectingmultiplescriptsandstylesheets'\u003e\u003c/a\u003eInjecting multiple scripts and stylesheets\n\nYou can inject multiple scripts and stylesheets by passing an array of strings or option objects:\n\n```ts\nsetupContentScriptActivation({\n  inject: {\n    scripts: [\"content-script.js\", \"content-script-2.js\"],\n    styles: [\"content-style.css\", \"content-style-2.css\"],\n  },\n});\n```\n\nNote that you need to call `setupActivation` **from every content script** you want to inject.\n\n### \u003ca name='Scriptsshorthands'\u003e\u003c/a\u003eScripts shorthands\n\nFor brevity, `setupContentScriptActivation` has two shorthand APIs:\n\n- If you don't need to pass any other options, you can pass the script or scripts to inject directly in string form:\n\n  ```ts\n  setupContentScriptActivation(\"content-script.js\");\n  ```\n\n- If you need to pass other options, but don't need any of the `inject` options, you can pass the script or scripts to inject directly to `inject`:\n\n  ```ts\n  setupContentScriptActivation({\n    filterTab: (tab) =\u003e tab.url?.startsWith(\"http\"),\n    inject: \"content-script.js\",\n  });\n  ```\n\n### \u003ca name='Omittingtheactivationcallback'\u003e\u003c/a\u003eOmitting the activation callback\n\nIf you don't need to run any code in your content script on activation (for example, if you only want to make sure that the script and styles are only injected once), you can omit the callback when calling `setupActivation`:\n\n```ts\nimport { setupActivation } from \"content-script-activation\";\n\nsetupActivation();\n```\n\nNote that you still need to call `setupActivation` from every content script you want to inject.\n\n### \u003ca name='Multipleinstances'\u003e\u003c/a\u003eMultiple instances\n\nIf you want to use `setupContentScriptActivation` more than once, you **must** pass a unique ID to each instance:\n\n```ts\n// service-worker.js\nsetupContentScriptActivation({\n  // ...\n  inject: \"content-script-1.js\",\n  scriptId: \"content-script-1\",\n});\nsetupContentScriptActivation({\n  // ...\n  inject: \"content-script-2.js\",\n  scriptId: \"content-script-2\",\n});\n\n// content-script-1.js\nsetupActivation(() =\u003e {\n  // ...\n}, \"content-script-1\");\n\n// content-script-2.js\nsetupActivation(() =\u003e {\n  // ...\n}, \"content-script-2\");\n```\n\nAn example use case for this is when you want to inject different scripts on different tabs. In this case, you can use the `filterTab` option to filter the tabs where each script is injected.\n\n### \u003ca name='Manualactivation'\u003e\u003c/a\u003eManual activation\n\nBy default, the content script is activated when the extension icon is clicked. For advanced use cases, you can pass `false` to the `injectOnClick` option. This will disable the default behavior, and `setupContentScriptActivation` will return an asynchronous function that you can call to activate the content script manually. The function takes a target as an argument, which corresponds to the `target` option of [`browser.scripting.executeScript`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/executeScript) ([`scripting.InjectionTarget`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/InjectionTarget)).\n\n```ts\nconst activate = setupContentScriptActivation({\n  inject: \"content-script.js\",\n  injectOnClick: false,\n});\n\n// when you want to activate the content script:\nawait activate({ tabId: myTabId });\n```\n\n## \u003ca name='Browsersupport'\u003e\u003c/a\u003eBrowser support\n\nAll browsers that support the underlying APIs should be supported. This is the case for Chrome and Firefox, and probably all desktop browsers that support extensions in the first place. Cross-browser API namespace compatibility is achieved through the [`browser-namespace`](https://github.com/DaniGuardiola/browser-namespace) package.\n\n## \u003ca name='Featuresunderconsideration'\u003e\u003c/a\u003eFeatures under consideration\n\n- Support `browser.scripting.removeCSS`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaniguardiola%2Fcontent-script-activation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdaniguardiola%2Fcontent-script-activation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdaniguardiola%2Fcontent-script-activation/lists"}