Skip to content

Add new scriptlets — 'edit-object-on-getter' and 'edit-object-on-setter' #568

Description

@slvvko

uBlock Origin recently added four new scriptlets that AdGuard has no equivalent for — (commit 99e6228):

  • edit-object-on-getter.js — prune properties from an object on read access
  • trusted-edit-object-on-getter.js — edit (set new values) properties of an object on read access
  • edit-object-on-setter.js — prune properties from an object on write access
  • trusted-edit-object-on-setter.js — edit (set new values) properties of an object on write access

How they work

Scriptlet intercepts property descriptor:
  propChain (e.g. "window.__data")
       ↓
  getter trap OR setter trap
       ↓
  JSONPath query applied (prune / edit)
       ↓
  Modified object returned to / stored for page scripts

The core mechanism uses trapProperty that walks the property chain, replaces the descriptor with { get, set } traps, and on access applies JSONPath edits.

Gap analysis

AdGuard scriptlet What it does Why it's not equivalent
set-constant / trusted-set-constant Replaces entire property value No selective editing — can't say "remove trackingId but keep userName"
json-prune / json-prune-fetch-response / json-prune-xhr-response Intercepts JSON.parse / fetch / XHR Only works on network/parse boundaries, not arbitrary property access
trusted-json-set Intercepts method calls via Proxy Intercepts function calls, not property descriptor access
abort-on-property-read/write Throws ReferenceError on access Only aborts — doesn't modify values
trusted-prune-inbound-object Proxy on native methods, prunes arguments Prunes function arguments, not property descriptor returns

None of these can do: "When window.__CONFIG is read, return it with trackingPreferences removed."

Real-world use case

// Site sets:
window.__INITIAL_STATE__ = {
  user: { name: "Alice", id: "123", trackingId: "abc-def", adPreferences: {...} },
  config: { theme: "dark", analyticsEnabled: true }
};

// Desired: when any script reads window.__INITIAL_STATE__,
// `trackingId` and `adPreferences` are pruned, `analyticsEnabled` set to false

// example.com##+js(edit-object-on-getter, window.__INITIAL_STATE__, $.user.trackingId $.user.adPreferences $.config.analyticsEnabled)
// Not possible with any existing AdGuard scriptlet

Proposed implementation

1. New helper

A shared helper that defines getter/setter traps on a property chain and applies a configurable edit callback. Can reuse:

  • getPropertyInChain() — chain walking (get-property-in-chain.ts)
  • setPropertyAccess() — safe Object.defineProperty wrapper (object-utils.ts)
  • interceptChainProp() — intermediate chain trap (chain-prop-utils.ts)
  • getDescriptorAddon() — infinite-loop prevention (get-descriptor-addon.ts)
  • jsonPruner() / jsonPath() — JSON mutation (prune-utils.ts, json-path-utils.ts)

2. Four new scriptlet files

File Uses
src/scriptlets/edit-object-on-getter.js jsonPruner for prune-only
src/scriptlets/edit-object-on-setter.js jsonPruner for prune-only
src/scriptlets/trusted-edit-object-on-getter.js jsonPath for full edit
src/scriptlets/trusted-edit-object-on-setter.js jsonPath for full edit

Each accepts: propChain (dot-separated path) + variadic JSONPath expressions.

3. Test files (4), compatibility table updates, auto-generated wiki docs

Design decisions

  1. JSONPath syntax: Use AdGuard's existing JSONPath syntax from json-path-utils.ts. uBO compatibility aliases may be needed for cross-blocker filter lists.
  2. Non-trusted scope: Follow uBO's security boundary — only pruning, no value injection.
  3. Chain trapping: Follow set-constant pattern — trap intermediate null/undefined props so the trap activates when the chain materializes.
  4. Stack matching: Consider adding optional stack parameter for targeting specific call sites (uBO doesn't include it, but AdGuard's set-constant does).

Related

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions