Skip to content

Commit 1ed9b5f

Browse files
committed
add trusted types api support for trusted-create-element. #507
AG-43305 Squashed commit of the following: commit d2a1132 Author: slvvko <v.leleka@adguard.com> Date: Mon Feb 2 20:19:45 2026 -0500 add trusted types api support for trusted-create-element commit 04c7683 Author: slvvko <v.leleka@adguard.com> Date: Mon Feb 2 19:43:23 2026 -0500 fix md formatting in readme
1 parent 95d53f9 commit 1ed9b5f

4 files changed

Lines changed: 117 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
1818
like `new Promise()` or `new MutationObserver()` [#461].
1919
- `remove-request-query-parameter` scriptlet to remove query parameters
2020
from `fetch` and `XMLHttpRequest` requests [#329].
21+
- [Trusted Types API] support in `trusted-create-element` scriptlet [#507].
2122

2223
### Fixed
2324

@@ -39,6 +40,7 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
3940
[#422]: https://github.com/AdguardTeam/Scriptlets/issues/422
4041
[#461]: https://github.com/AdguardTeam/Scriptlets/issues/461
4142
[#486]: https://github.com/AdguardTeam/Scriptlets/issues/486
43+
[#507]: https://github.com/AdguardTeam/Scriptlets/issues/507
4244

4345
## [v2.2.15] - 2026-01-22
4446

@@ -207,7 +209,7 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
207209
- Ability to set random response content in `prevent-fetch` scriptlet [#416].
208210
- Ability to choose CSS injection method in `inject-css-in-shadow-dom` scriptlet [#477].
209211
- TypeScript types for CoreLibs provided [`ContentScriptApi`](./README.md#scriptlets-api--content-script-api).
210-
- Trusted Types API utility — [`PolicyApi`](./README.md#scriptlets-api--content-script-api--policy-api).
212+
- [Trusted Types API] utility — [`PolicyApi`](./README.md#scriptlets-api--content-script-api--policy-api).
211213

212214
### Changed
213215

@@ -294,7 +296,7 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
294296
### Added
295297

296298
- `prevent-canvas` scriptlet [#451].
297-
- `trusted-types` policy to `trusted-replace-node-text` scriptlet [#457].
299+
- [Trusted Types API] support in `trusted-replace-node-text` scriptlet [#457].
298300
- `parentSelector` option to search for nodes for `remove-node-text` scriptlet [#397].
299301
- `transform` option with `base64decode` value for `href-sanitizer` scriptlet [#455].
300302
- `removeParam` and `removeHash` values in `transform` option for `href-sanitizer` scriptlet [#460].
@@ -911,3 +913,5 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
911913
[#255]: https://github.com/AdguardTeam/Scriptlets/issues/255
912914

913915
[@adguard/agtree]: https://www.npmjs.com/package/@adguard/agtree
916+
917+
[Trusted Types API]: https://developer.mozilla.org/docs/Web/API/Trusted_Types_API

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -882,12 +882,12 @@ declare function convertAdgRedirectToUbo(rule: string): string;
882882

883883
| Browser | Version |
884884
|-----------------------|:--------|
885-
| Chrome |55 |
886-
| Firefox |52 |
887-
| Edge |15 |
888-
| Opera |42 |
889-
| Safari |13 |
890-
| Internet Explorer | |
885+
| Chrome |55 |
886+
| Firefox |52 |
887+
| Edge |15 |
888+
| Opera |42 |
889+
| Safari |13 |
890+
| Internet Explorer ||
891891

892892
## <a name="used-by"> Projects using Scriptlets
893893

src/scriptlets/trusted-create-element.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
nativeIsNaN,
66
parseAttributePairs,
77
getErrorMessage,
8+
getTrustedTypesApi,
89
} from '../helpers';
910
import type { ParsedAttributePair } from '../helpers';
1011
import { type Source } from './scriptlets';
@@ -100,10 +101,17 @@ export function trustedCreateElement(
100101
logMessage(source, `${prefix} due to ${getErrorMessage(error)}`);
101102
};
102103

104+
const trustedTypesApi = getTrustedTypesApi(source);
105+
103106
let element: HTMLElement;
104107
try {
105108
element = document.createElement(tagName);
106-
element.textContent = textContent;
109+
// For script elements, use Trusted Types API
110+
if (tagName.toLowerCase() === 'script' && textContent) {
111+
element.textContent = trustedTypesApi.createScript(textContent);
112+
} else {
113+
element.textContent = textContent;
114+
}
107115
} catch (e) {
108116
logError(`Cannot create element with tag name '${tagName}'`, e);
109117
return;
@@ -120,7 +128,13 @@ export function trustedCreateElement(
120128

121129
attributes.forEach((attr) => {
122130
try {
123-
element.setAttribute(attr.name, attr.value);
131+
// Use Trusted Types API for attributes that require it
132+
const trustedValue = trustedTypesApi.convertAttributeToTrusted(
133+
tagName,
134+
attr.name,
135+
attr.value,
136+
);
137+
element.setAttribute(attr.name, trustedValue);
124138
} catch (e) {
125139
logError(`Cannot set attribute '${attr.name}' with value '${attr.value}'`, e);
126140
}
@@ -206,4 +220,5 @@ trustedCreateElement.injections = [
206220
nativeIsNaN,
207221
parseAttributePairs,
208222
getErrorMessage,
223+
getTrustedTypesApi,
209224
];

tests/scriptlets/trusted-create-element.test.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,3 +329,91 @@ test('creating iframe', (assert) => {
329329

330330
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
331331
});
332+
333+
test('creating script element with textContent', (assert) => {
334+
const { children } = createRoot(ROOT_ID);
335+
336+
const childTagName = 'script';
337+
const textContent = 'window.scriptTestVar = "trusted-create-element-script";';
338+
339+
assert.strictEqual(children.length, 0, 'Parent element has no children before scriptlet is run');
340+
341+
runScriptlet(name, [ROOT_SELECTOR, childTagName, '', textContent]);
342+
343+
assert.strictEqual(children.length, 1, 'Script element was appended');
344+
345+
const child = children[0];
346+
347+
assert.strictEqual(child.tagName.toLowerCase(), childTagName, 'Tag name is set correctly');
348+
assert.strictEqual(child.textContent, textContent, 'Text content is set correctly');
349+
assert.strictEqual(window.scriptTestVar, 'trusted-create-element-script', 'Script was executed');
350+
351+
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
352+
353+
clearGlobalProps('scriptTestVar');
354+
});
355+
356+
test('creating script element with attributes', (assert) => {
357+
const { children } = createRoot(ROOT_ID);
358+
359+
const childTagName = 'script';
360+
const attributesParam = 'type="text/javascript" data-test="value"';
361+
const textContent = 'window.scriptTestVar2 = "script-with-attrs";';
362+
363+
assert.strictEqual(children.length, 0, 'Parent element has no children before scriptlet is run');
364+
365+
runScriptlet(name, [ROOT_SELECTOR, childTagName, attributesParam, textContent]);
366+
367+
assert.strictEqual(children.length, 1, 'Script element was appended');
368+
369+
const child = children[0];
370+
371+
assert.strictEqual(child.tagName.toLowerCase(), childTagName, 'Tag name is set correctly');
372+
assert.strictEqual(child.getAttribute('type'), 'text/javascript', 'Type attribute is set correctly');
373+
assert.strictEqual(child.getAttribute('data-test'), 'value', 'Data attribute is set correctly');
374+
assert.strictEqual(child.textContent, textContent, 'Text content is set correctly');
375+
assert.strictEqual(window.scriptTestVar2, 'script-with-attrs', 'Script was executed');
376+
377+
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
378+
379+
clearGlobalProps('scriptTestVar2');
380+
});
381+
382+
test('trustedTypes.createScript is called for script textContent', (assert) => {
383+
if (!window.trustedTypes) {
384+
assert.expect(0);
385+
return;
386+
}
387+
388+
const { children } = createRoot(ROOT_ID);
389+
390+
const nativeCreatePolicy = window.trustedTypes.createPolicy.bind(window.trustedTypes);
391+
let createScriptCalled = false;
392+
let createScriptArg = null;
393+
394+
window.trustedTypes.createPolicy = (name, rules) => {
395+
const wrappedRules = {
396+
...rules,
397+
createScript: (input) => {
398+
createScriptCalled = true;
399+
createScriptArg = input;
400+
return rules.createScript ? rules.createScript(input) : input;
401+
},
402+
};
403+
return nativeCreatePolicy(name, wrappedRules);
404+
};
405+
406+
const childTagName = 'script';
407+
const textContent = 'window.scriptTestVar3 = "trusted-types-verify";';
408+
409+
runScriptlet(name, [ROOT_SELECTOR, childTagName, '', textContent]);
410+
411+
assert.strictEqual(children.length, 1, 'Script element was appended');
412+
assert.true(createScriptCalled, 'trustedTypes createScript was called');
413+
assert.strictEqual(createScriptArg, textContent, 'createScript was called with correct textContent');
414+
assert.strictEqual(window.scriptTestVar3, 'trusted-types-verify', 'Script was executed');
415+
assert.strictEqual(window.hit, 'FIRED', 'hit fired');
416+
417+
window.trustedTypes.createPolicy = nativeCreatePolicy;
418+
clearGlobalProps('scriptTestVar3');
419+
});

0 commit comments

Comments
 (0)