Skip to content

Commit 320efda

Browse files
committed
AG-21246 Enhance logging in 'trusted-json-set'
Squashed commit of the following: commit d3c03ef Author: Adam Wróblewski <adam@adguard.com> Date: Mon Apr 13 13:29:14 2026 +0200 Fix logging with stack commit db9a719 Author: Adam Wróblewski <adam@adguard.com> Date: Sat Apr 11 20:47:07 2026 +0200 Enhance 'trusted-json-set' and related utilities — support logging-only mode, JSONPath matching, and mutation callbacks commit ff1032a Author: Adam Wróblewski <adam@adguard.com> Date: Fri Apr 10 14:04:50 2026 +0200 Update changelog commit 734ca09 Author: Adam Wróblewski <adam@adguard.com> Date: Fri Apr 10 12:59:20 2026 +0200 Enhance logging in 'trusted-json-set' — add structured cloning support and improve debug messages
1 parent 0435d8e commit 320efda

8 files changed

Lines changed: 839 additions & 48 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
2323

2424
### Changed
2525

26+
- `trusted-json-set` now supports method-only logging, filtered log-only output,
27+
JSONPath-based log-only filters, and verbose logs only when a write actually happens [#308].
2628
- `log-addEventListener` scriptlet: added new optional `noProtect` parameter,
2729
improving compatibility with other scriptlets that need to override `addEventListener` [#551].
2830

2931
### Fixed
3032

33+
- Logging original object in `trusted-json-set` scriptlet, previously original and modified object were pointing
34+
to the same reference, so the same content was logged, now deep copy is created for original object [#308].
3135
- `trusted-click-element` no longer throws when event handlers set `cancelBubble`
3236
on spoofed events [#555].
3337

src/helpers/json-path-utils.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ export function buildJsonPathExpression(selectorPath: string, argumentValue: any
243243
* @param nativeObjects optional bag of native object references
244244
* @param onMutation optional callback fired once if any mutation happens
245245
* @param stack optional stack trace pattern that must match for processing to occur
246+
* @param matchOnly when true, evaluates guards and selector matches without mutating `root`
246247
* @returns mutated root value
247248
*/
248249
export const jsonPath = (
@@ -252,6 +253,7 @@ export const jsonPath = (
252253
nativeObjects: JsonPathNativeObjects,
253254
onMutation?: JsonPathMutationObserver,
254255
stack = '',
256+
matchOnly = false,
255257
): any => {
256258
const ROOT_PATH = '$';
257259
const DOT = '.';
@@ -1769,12 +1771,19 @@ export const jsonPath = (
17691771
}
17701772
}
17711773

1774+
const matches = evaluateSelector(root, command.selector);
1775+
if (matchOnly) {
1776+
if (matches.length > 0 && onMutation) {
1777+
onMutation();
1778+
}
1779+
return root;
1780+
}
1781+
17721782
if (!isMutationAllowed(command.mutation.mode)) {
17731783
logMessage(source, 'JSONPath set and append operations are allowed only in trusted scriptlets');
17741784
return root;
17751785
}
17761786

1777-
const matches = evaluateSelector(root, command.selector);
17781787
if (command.mutation.mode === 'remove') {
17791788
removeMatches(matches);
17801789
if (didMutate && onMutation) {
@@ -1795,3 +1804,44 @@ export const jsonPath = (
17951804

17961805
return root;
17971806
};
1807+
1808+
/**
1809+
* Checks whether a JSON value matches a JSONPath selector without mutating it.
1810+
*
1811+
* Supports the same selector syntax and leading guards as `jsonPath`, but only
1812+
* reports whether the selector matched at least one candidate.
1813+
*
1814+
* @param source source descriptor used for logging and parsing
1815+
* @param root JSON object or array to inspect
1816+
* @param path JSONPath expression used for matching
1817+
* @param nativeObjects optional bag of native object references
1818+
* @param stack optional stack trace pattern that must match for processing to occur
1819+
* @returns true when the expression matches the input value
1820+
*/
1821+
export const matchesJsonPath = (
1822+
source: Source,
1823+
root: Record<string, any>,
1824+
path: string,
1825+
nativeObjects: JsonPathNativeObjects,
1826+
stack = '',
1827+
): boolean => {
1828+
if (!path) {
1829+
return true;
1830+
}
1831+
1832+
let didMatch = false;
1833+
1834+
jsonPath(
1835+
source,
1836+
root,
1837+
path,
1838+
nativeObjects,
1839+
() => {
1840+
didMatch = true;
1841+
},
1842+
stack,
1843+
true,
1844+
);
1845+
1846+
return didMatch;
1847+
};

src/helpers/log-message.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ export const logMessage = (
3333
// Template literals convert object to string,
3434
// so 'message' should not be passed to template literals
3535
// as it will not be logged correctly
36-
nativeConsole(`${name}:`, message);
36+
if (Array.isArray(message)) {
37+
nativeConsole(`${name}:`, ...message);
38+
} else {
39+
nativeConsole(`${name}:`, message);
40+
}
3741
return;
3842
}
3943

src/helpers/prune-utils.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ export const jsonPruner = (
199199
* @param requiredPaths array of required property chains that must all be present for the set to occur
200200
* @param stack string which should be matched by stack trace
201201
* @param nativeObjects reference to native objects
202+
* @param onMutation optional callback fired after each successful write
202203
* @returns the modified root object
203204
*/
204205
export const jsonSetter = (
@@ -210,9 +211,17 @@ export const jsonSetter = (
210211
requiredPaths: { path: string; value?: any }[],
211212
stack: string,
212213
nativeObjects: any,
214+
onMutation?: () => void,
213215
): ArbitraryObject => {
214216
const { nativeStringify } = nativeObjects;
215217

218+
const notifyMutation = () => {
219+
hit(source);
220+
if (onMutation) {
221+
onMutation();
222+
}
223+
};
224+
216225
if (!setPath) {
217226
logMessage(
218227
source,
@@ -243,7 +252,7 @@ export const jsonSetter = (
243252
const node = matchedNodes[i];
244253
if (node && node.base) {
245254
node.base[node.prop] = getValue(node.base[node.prop]);
246-
hit(source);
255+
notifyMutation();
247256
}
248257
}
249258
} else if (!hasWildcard && valueFilter === undefined) {
@@ -264,7 +273,7 @@ export const jsonSetter = (
264273
}
265274
const lastPart = pathParts[pathParts.length - 1];
266275
current[lastPart] = getValue(current[lastPart]);
267-
hit(source);
276+
notifyMutation();
268277
}
269278
} catch (e) {
270279
logMessage(source, e);

0 commit comments

Comments
 (0)