@addon-core/inject-css 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +224 -72
- package/dist/index.cjs +556 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +90 -9
- package/dist/index.js +543 -79
- package/dist/index.js.map +1 -1
- package/package.json +29 -10
package/README.md
CHANGED
|
@@ -1,125 +1,277 @@
|
|
|
1
1
|
# @addon-core/inject-css
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@addon-core/inject-css)
|
|
4
|
-
[](https://www.npmjs.com/package/@addon-core/inject-css)
|
|
5
|
-
[](https://www.npmjs.com/package/@addon-core/inject-css)
|
|
4
|
+
[](https://www.npmjs.com/package/@addon-core/inject-css)
|
|
5
|
+
[](https://github.com/addon-stack/inject-css/actions/workflows/ci.yml)
|
|
6
|
+
[](LICENSE.md)
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
Automatically detects Chrome Extension Manifest V2 and V3 and delegates to the appropriate API via [@addon-core/browser](https://github.com/addon-stack/browser).
|
|
8
|
+
Insert and remove CSS code or extension stylesheets in browser tabs with one typed API for Manifest V2 and Manifest V3.
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
`@addon-core/inject-css` selects the correct native adapter, validates the target before delivery, and keeps unsupported browser behavior explicit.
|
|
12
11
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
- [Options](#options)
|
|
20
|
-
- [Examples](#examples)
|
|
21
|
-
- [License](#license)
|
|
12
|
+
- One target model for the top frame, all frames, selected frames, or selected documents
|
|
13
|
+
- Runtime validation that matches the TypeScript contract
|
|
14
|
+
- Ordered stylesheet injection
|
|
15
|
+
- Matching stylesheet removal where the browser exposes a native removal API
|
|
16
|
+
- Stable package errors for invalid, unsupported, failed, and timed-out operations
|
|
17
|
+
- No silent selector fallback and no extra frame-enumeration permissions
|
|
22
18
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
### npm:
|
|
19
|
+
## Install
|
|
26
20
|
|
|
27
21
|
```bash
|
|
28
22
|
npm install @addon-core/inject-css
|
|
29
23
|
```
|
|
30
24
|
|
|
31
|
-
### pnpm:
|
|
32
|
-
|
|
33
25
|
```bash
|
|
34
26
|
pnpm add @addon-core/inject-css
|
|
35
27
|
```
|
|
36
28
|
|
|
37
|
-
|
|
29
|
+
Your extension still needs the native permissions required for CSS injection, including `scripting` in MV3 and appropriate host or `activeTab` access. The package does not modify the manifest.
|
|
38
30
|
|
|
39
|
-
|
|
40
|
-
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import injectCss from "@addon-core/inject-css";
|
|
35
|
+
|
|
36
|
+
const injector = injectCss({
|
|
37
|
+
target: {tabId: 123},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await injector.insert("body { background: #f5f5f5; }");
|
|
41
|
+
|
|
42
|
+
// The source, target, and origin match the insertion.
|
|
43
|
+
await injector.remove("body { background: #f5f5f5; }");
|
|
41
44
|
```
|
|
42
45
|
|
|
46
|
+
The package detects the current manifest version automatically. Insertion uses `tabs.insertCSS` in MV2 and `scripting.insertCSS` in MV3; removal uses the matching native removal API when available.
|
|
47
|
+
|
|
48
|
+
## Choose what to target
|
|
49
|
+
|
|
50
|
+
Every injector has exactly one target. Selectors are mutually exclusive in TypeScript and validated again at runtime.
|
|
43
51
|
|
|
44
|
-
|
|
52
|
+
| Need | Target |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| Main frame | `{tabId: 123}` |
|
|
55
|
+
| Every injectable frame | `{tabId: 123, allFrames: true}` |
|
|
56
|
+
| One frame | `{tabId: 123, frameIds: [7]}` |
|
|
57
|
+
| Selected frames | `{tabId: 123, frameIds: [0, 7, 12]}` |
|
|
58
|
+
| Selected documents | `{tabId: 123, documentIds: ["document-a", "document-b"]}` |
|
|
45
59
|
|
|
46
60
|
```ts
|
|
47
|
-
|
|
61
|
+
const topFrame = injectCss({
|
|
62
|
+
target: {tabId: 123},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const selectedFrames = injectCss({
|
|
66
|
+
target: {tabId: 123, frameIds: [0, 7]},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const allFrames = injectCss({
|
|
70
|
+
target: {tabId: 123, allFrames: true},
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`allFrames` accepts only the literal `true`. Omitting a selector means the top frame; there is no `allFrames: false` mode.
|
|
48
75
|
|
|
49
|
-
|
|
50
|
-
const injector = injectCss({tabId: 123});
|
|
76
|
+
`documentIds` require an MV3 runtime with native document targeting. An unsupported target throws `UnsupportedInjectCssTargetError`; the package never drops `documentIds` or falls back to a broader target.
|
|
51
77
|
|
|
52
|
-
|
|
53
|
-
await injector.insert("body { background-color: #f0f0f0; }");
|
|
78
|
+
`allFrames` remains one native browser operation. The package does not enumerate frames or promise an exhaustive frame snapshot.
|
|
54
79
|
|
|
55
|
-
|
|
56
|
-
await injector.file("styles/main.css");
|
|
57
|
-
await injector.file(["styles/reset.css", "styles/theme.css"]);
|
|
80
|
+
## Insert CSS code
|
|
58
81
|
|
|
59
|
-
|
|
60
|
-
injector.
|
|
61
|
-
|
|
82
|
+
```ts
|
|
83
|
+
await injector.insert(`
|
|
84
|
+
html {
|
|
85
|
+
color-scheme: dark;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
body {
|
|
89
|
+
background: #111;
|
|
90
|
+
color: #eee;
|
|
91
|
+
}
|
|
92
|
+
`);
|
|
62
93
|
```
|
|
63
94
|
|
|
64
|
-
|
|
95
|
+
The CSS source must be a non-empty string.
|
|
65
96
|
|
|
66
|
-
|
|
97
|
+
## Insert CSS files
|
|
67
98
|
|
|
68
|
-
|
|
99
|
+
```ts
|
|
100
|
+
await injector.file("styles/content.css");
|
|
69
101
|
|
|
70
|
-
|
|
102
|
+
await injector.file([
|
|
103
|
+
"styles/reset.css",
|
|
104
|
+
"styles/theme.css",
|
|
105
|
+
]);
|
|
106
|
+
```
|
|
71
107
|
|
|
72
|
-
|
|
108
|
+
File lists must be non-empty and every path must be a non-empty string. Files are injected in the provided order. In MV2, one file completes for the requested target before the next file starts, preserving CSS cascade order.
|
|
73
109
|
|
|
74
|
-
|
|
110
|
+
## Remove CSS
|
|
75
111
|
|
|
76
|
-
|
|
112
|
+
Remove CSS code with `remove()` and extension stylesheets with `removeFile()`:
|
|
77
113
|
|
|
78
|
-
|
|
114
|
+
```ts
|
|
115
|
+
await injector.remove("body { background: #f5f5f5; }");
|
|
79
116
|
|
|
80
|
-
|
|
117
|
+
await injector.removeFile("styles/content.css");
|
|
81
118
|
|
|
82
|
-
|
|
119
|
+
await injector.removeFile([
|
|
120
|
+
"styles/reset.css",
|
|
121
|
+
"styles/theme.css",
|
|
122
|
+
]);
|
|
123
|
+
```
|
|
83
124
|
|
|
84
|
-
|
|
85
|
-
- `file(files: string | string[]): Promise<void>` — Inject one or more CSS files.
|
|
86
|
-
- `options(opts: Partial<InjectCssOptions>): this` — Update injector options.
|
|
125
|
+
Removal uses the injector's current target and origin. The CSS source, file list, target, and origin must match the values used for insertion. Removing a stylesheet that is not present is a native no-op.
|
|
87
126
|
|
|
88
|
-
|
|
127
|
+
MV3 uses `scripting.removeCSS`. MV2 uses `tabs.removeCSS` only when the current browser exposes it; otherwise removal rejects with `UnsupportedInjectCssOperationError`. This capability is checked when removal is requested, so insertion remains available in MV2 browsers without `tabs.removeCSS`.
|
|
89
128
|
|
|
90
|
-
|
|
129
|
+
MV2 removes multiple files sequentially in the provided order, matching its insertion behavior. `runAt` affects insertion only because the MV2 removal API has no corresponding field.
|
|
91
130
|
|
|
92
|
-
|
|
93
|
-
| --------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
94
|
-
| tabId | number | Required. Target browser tab ID. |
|
|
95
|
-
| frameId | boolean \| number \| number[] | Optional. Select frames to inject into: `true` for all frames; number or array for specific. |
|
|
96
|
-
| matchAboutBlank | boolean | Optional. (V2 only) Include `about:blank` and similar subframes. Defaults to `true`. |
|
|
97
|
-
| runAt | 'document_start' \| 'document_end' \| 'document_idle' | Optional. (V2 only) Injection timing, matches Chrome's `runAt` in `insertCSS`. |
|
|
98
|
-
| documentId | string \| string[] | Optional. (V3 only) Document IDs for scripting targets. |
|
|
99
|
-
| origin | 'AUTHOR' \| 'USER' | Optional. CSS origin matching Chrome's API (`cssOrigin` in V2, `origin` in V3). |
|
|
131
|
+
## Reuse an injector
|
|
100
132
|
|
|
101
|
-
|
|
133
|
+
Replace the complete target with `target()`:
|
|
102
134
|
|
|
103
135
|
```ts
|
|
104
|
-
|
|
136
|
+
injector
|
|
137
|
+
.target({tabId: 123, frameIds: [7]})
|
|
138
|
+
.target({tabId: 123, allFrames: true});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The second call replaces the previous selector instead of merging with it. A validation failure leaves the existing target unchanged.
|
|
142
|
+
|
|
143
|
+
Update only execution options with `options()`:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
injector.options({
|
|
147
|
+
origin: "USER",
|
|
148
|
+
timeoutMs: 8_000,
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`options()` never accepts or changes a target. Passing an explicit `undefined` resets that option instead of retaining its previous value:
|
|
105
153
|
|
|
106
|
-
|
|
154
|
+
```ts
|
|
155
|
+
injector.options({
|
|
156
|
+
origin: undefined,
|
|
157
|
+
timeoutMs: undefined,
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The next operation then uses the native origin default and the package's default timeout.
|
|
162
|
+
|
|
163
|
+
## Execution options
|
|
164
|
+
|
|
165
|
+
The portable baseline is to omit adapter-specific options:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
107
168
|
const injector = injectCss({
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
documentId: "main-doc-id", // (V3 only)
|
|
112
|
-
origin: "AUTHOR", // 'AUTHOR' or 'USER'
|
|
169
|
+
target: {tabId: 123},
|
|
170
|
+
origin: "AUTHOR",
|
|
171
|
+
timeoutMs: 5_000,
|
|
113
172
|
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
| Option | MV2 | MV3 |
|
|
176
|
+
| --- | --- | --- |
|
|
177
|
+
| `origin` | Mapped to `author` or `user` | Passed as `AUTHOR` or `USER` |
|
|
178
|
+
| `timeoutMs` | Supported; default `4_000` ms | Supported; default `4_000` ms |
|
|
179
|
+
| `matchAboutBlank` | Passed only when explicitly set | Rejected; no native equivalent |
|
|
180
|
+
| `runAt` | Passed to `tabs.insertCSS` | Rejected; no native equivalent |
|
|
181
|
+
|
|
182
|
+
When `matchAboutBlank` is omitted, the package preserves the native default instead of forcing it to `true`.
|
|
183
|
+
|
|
184
|
+
Explicit unsupported options throw `UnsupportedInjectCssOptionError`. They are never ignored silently.
|
|
185
|
+
|
|
186
|
+
## Handle failures
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
import {InjectCssBaseError} from "@addon-core/inject-css";
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
await injector.file("styles/content.css");
|
|
193
|
+
} catch (error) {
|
|
194
|
+
if (error instanceof InjectCssBaseError) {
|
|
195
|
+
console.error(error.code, error.message, error.cause);
|
|
196
|
+
} else {
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Every rejected package operation exposes an error derived from `InjectCssBaseError` with a stable `code`. Delivery and timeout errors also retain the request target and expose `operation` as `"insert"` or `"remove"`. For explicit MV2 frame targets, a delivery error may contain an `InjectCssFrameDeliveryError` cause with code `ERR_INJECT_CSS_FRAME_DELIVERY`, the failed `tabId`, `frameId`, operation, and native cause. Prefer `code` when errors may cross realms or multiple copies of the dependency may exist.
|
|
114
203
|
|
|
115
|
-
|
|
116
|
-
await injector.insert("body { background-color: #fafafa; }");
|
|
204
|
+
Known validation and adapter incompatibilities fail before delivery. Browser capabilities discovered only by a native call are normalized after that call.
|
|
117
205
|
|
|
118
|
-
|
|
119
|
-
|
|
206
|
+
### What `Promise<void>` means
|
|
207
|
+
|
|
208
|
+
Native CSS injection and removal APIs do not provide a portable per-frame result. `insert()`, `file()`, `remove()`, and `removeFile()` therefore resolve with no value.
|
|
209
|
+
|
|
210
|
+
A resolved promise means the native operation completed. It does not prove that CSS was inserted or removed in every requested frame. A rejected multi-target operation is not transactional: some targets or earlier files may already have completed the requested change.
|
|
211
|
+
|
|
212
|
+
In MV2, a timeout stops the package from starting later files in a sequential insertion or removal batch. In MV3, the complete file list is handed to the browser in one native call before a timeout can occur. In either adapter, a timeout cannot cancel a native browser operation that is already in progress.
|
|
213
|
+
|
|
214
|
+
## API reference
|
|
215
|
+
|
|
216
|
+
The factory is available as both a default and named export:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import injectCss from "@addon-core/inject-css";
|
|
220
|
+
import {injectCss} from "@addon-core/inject-css";
|
|
120
221
|
```
|
|
121
222
|
|
|
223
|
+
```ts
|
|
224
|
+
interface InjectCssContract {
|
|
225
|
+
insert(css: string): Promise<void>;
|
|
226
|
+
file(files: string | NonEmptyReadonlyArray<string>): Promise<void>;
|
|
227
|
+
remove(css: string): Promise<void>;
|
|
228
|
+
removeFile(files: string | NonEmptyReadonlyArray<string>): Promise<void>;
|
|
229
|
+
target(target: InjectCssTarget): this;
|
|
230
|
+
options(options: InjectCssExecutionOptionsPatch): this;
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Runtime exports:
|
|
235
|
+
|
|
236
|
+
```text
|
|
237
|
+
injectCss
|
|
238
|
+
InjectCssBaseError
|
|
239
|
+
InjectCssDeliveryError
|
|
240
|
+
InjectCssFrameDeliveryError
|
|
241
|
+
InjectCssTimeoutError
|
|
242
|
+
InvalidInjectCssCodeError
|
|
243
|
+
InvalidInjectCssFilesError
|
|
244
|
+
InvalidInjectCssOptionsError
|
|
245
|
+
InvalidInjectCssTargetError
|
|
246
|
+
UnsupportedInjectCssOptionError
|
|
247
|
+
UnsupportedInjectCssOperationError
|
|
248
|
+
UnsupportedInjectCssTargetError
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Core type exports:
|
|
252
|
+
|
|
253
|
+
```text
|
|
254
|
+
InjectCssContract
|
|
255
|
+
InjectCssOptions
|
|
256
|
+
InjectCssExecutionOptions
|
|
257
|
+
InjectCssExecutionOptionsPatch
|
|
258
|
+
InjectCssOperation
|
|
259
|
+
InjectCssOrigin
|
|
260
|
+
InjectCssTarget
|
|
261
|
+
InjectCssTopFrameTarget
|
|
262
|
+
InjectCssAllFramesTarget
|
|
263
|
+
InjectCssFramesTarget
|
|
264
|
+
InjectCssDocumentsTarget
|
|
265
|
+
InjectCssErrorCode
|
|
266
|
+
NonEmptyReadonlyArray
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Design boundaries
|
|
270
|
+
|
|
271
|
+
The package focuses on portable programmatic CSS insertion and removal. It does not enumerate frames, discover document IDs, track which stylesheets were inserted, aggregate application-specific per-frame results, or claim atomic delivery across targets.
|
|
272
|
+
|
|
273
|
+
Callers remain responsible for retaining the exact source, target, and origin needed for later removal.
|
|
122
274
|
|
|
123
275
|
## License
|
|
124
276
|
|
|
125
|
-
MIT
|
|
277
|
+
[MIT](LICENSE.md)
|