Detector perf monitoring - #3013
Conversation
Times every detector invocation (bot/fraud/adwall utils and config-driven webDetection scans) with a synchronous fire-and-forget wrapper, accumulates per-page counters (run count, worst single run, total, combined), and flushes threshold-crossing events through webEvents when the page is hidden — at most once per event type per page. Threshold bin edges come from remote config with code fallbacks. Nothing is emitted per run and no page-observable performance marks are created. Co-authored-by: Cursor <cursoragent@cursor.com>
…d exact stats for breakage reports - Emit measured/ran/threshold events as soon as they first become true instead of flushing on page-hidden, so killed processes lose nothing - Add detectorPerf_severe immediate event when a run crosses the highest configured edge, capped by maxSeverePerPage, with exact detector attribution (config IDs) in the data payload - Expose getStats() and attach exact per-detector timings to breakage report payloads - Capture performance.now at module load; own-property check on config-supplied threshold overrides Co-authored-by: Cursor <cursoragent@cursor.com>
3308b22 to
14dd001
Compare
Build Branch
Static preview entry points
QR codes (mobile preview)
Integration commandsnpm (Android / Extension): Swift Package Manager (Apple): .package(url: "https://github.com/duckduckgo/content-scope-scripts.git", branch: "pr-releases/detector-perf-monitoring")git submodule (Windows): git -C submodules/content-scope-scripts fetch origin pr-releases/detector-perf-monitoring
git -C submodules/content-scope-scripts checkout origin/pr-releases/detector-perf-monitoringPin to exact commitnpm (Android / Extension): Swift Package Manager (Apple): .package(url: "https://github.com/duckduckgo/content-scope-scripts.git", revision: "61792a766f3986c995bbabc9c8376d5d8d35ccff")git submodule (Windows): git -C submodules/content-scope-scripts fetch origin pr-releases/detector-perf-monitoring
git -C submodules/content-scope-scripts checkout 61792a766f3986c995bbabc9c8376d5d8d35ccff |
[Beta] Generated file diffTime updated: Wed, 09 Sep 2026 18:05:07 GMT Android
File has changed Apple
File has changed Chrome-mv3File has changed FirefoxFile has changed Integration
File has changed Windows
File has changed |
There was a problem hiding this comment.
Stale comment
Web Compatibility Assessment
File Lines Severity Finding injected/src/features/detector-perf.js371–376 info timeDetectorpreserves the synchronous detector contract: it returnsfn()'s value immediately and only firesreportDurationfire-and-forget. Noawaiton the hot path, so callers that expect sync return values (e.g. breakage-report detector utils) are unchanged.injected/src/features/web-detection.js64–78 info _evaluateMatchkeeps the existing try/catch →'error'behaviour inside the wrappedfn; only timing is added around it.injected/src/features/detector-perf.js89–90, 170–243 info Measurement deliberately avoids DOM/layout reads and performance.mark/measure; integration test (measurement leaves no page-observable performance timeline entries) verifies no detector-specific timeline pollution.injected/src/features/breakage-reporting.js62–63, 89 info timeDetectoradds twoperformance.nowreads per wrapped detector call. Cost is negligible relative to DOM-based detectors, but it runs even whendetectorPerfis disabled (recording is silently dropped).injected/src/features/breakage-reporting.js81–88 warning Breakage-report getStatsordering relies on fire-and-forgetreportDurationcompleting beforeawait getStats(). This works in practice whendetectorPerf._readyis already settled (typical at user-initiated report time) becauserecordruns synchronously after the await. If_readywere still pending,void reportDurationcould racegetStatsand produce an incomplete snapshot. Integration test covers the happy path; the FIFO-on-_readyrationale is fragile if init ordering changes.injected/src/captured-globals.js20 info performanceNowis captured without optional chaining (performance.now.bind(performance)). A missingperformanceAPI would throw at module load and break the entire bundle. All target WebViews/extension contexts provideperformance; risk is theoretical.injected/src/features.jsplatformSupport info detectorPerfis enabled only on apple-isolated, android, and windows — not extension/firefox/chrome builds. Extension detector paths are unaffected.Security Assessment
File Lines Severity Finding injected/src/captured-globals.js20 info New performanceNowcapture follows the established.bind(owner)pattern and is consumed only fromdetector-perf.js, reducing bypass risk from page-tamperedperformance.nowthrowing during detector execution.injected/src/features/detector-perf.js174, 195–196, 205 info hasOwnProperty.callondetectorOverridesblocks prototype-chain keys likeconstructor.NAME_PATTERNrejects malformedname/detailbefore they reach event-type construction or severe payloads.injected/src/features/detector-perf.js57–61, 159–162 info parseThresholdsandmaxSeverePerPagesanitize config input (finite positive numbers only; severe cap floors to positive integer).maxSeverePerPagelimits blast radius from a bad threshold push.injected/src/features/detector-perf.js333–342 info _dispatchpasses explicit{ type }or{ type, data }towebEvents.fireEvent— no spread of untrusted objects, sonativeDatacannot be forwarded.injected/src/features/detector-perf.js96–111 info Uses uncaptured native Map/Set(not imported fromcaptured-globals.js). At document-start load this is low risk; consistent with many other features but not maximal hardening.injected/src/features/detector-perf.js131–138, integration test info Feature is remote-config gated ( state: disabledemits nothing and omitsdetectorPerffrom breakage payloads). Rollback path exists without code deploy.injected/src/features/breakage-reporting.js89–91 info detectorPerfstats attach only to user-initiated breakage reports via existingbreakageDataencoding — no newpostMessageor cross-frame channel.Risk Level
Medium Risk — Adds a config-gated telemetry feature and a minimal
captured-globalscapture, wrapping existing detector call sites with sync timing overhead but no browser API overrides, prototype patches, messaging trust-boundary changes, or message-bridge modifications.Recommendations
- warning — breakage-report ordering: Before
getStats, explicitly drain pending recordings (e.g.await reportDurationfor breakage-reporttimeDetectorcalls, or adetectorPerf.flush()exposed method) instead of relying on_readymicrotask ordering.- info —
performanceNowcapture: Add optional chaining or a safe fallback (e.g.Date.now) so a missingperformanceAPI cannot fail module load for the whole bundle.- info — disabled overhead: Optionally skip
performanceNowintimeDetectorwhendetectorPerfis disabled/skipped (feature-setting check on the caller side) to avoid unnecessary work on every detector invocation fleet-wide.- info — captured intrinsics: Import
Map/Setfromcaptured-globals.jsfor consistency with global-capture hygiene.- info — tests (already strong): Integration coverage for disabled state, non-observable timeline, severe attribution, and breakage-report payload shape is good; consider one unit test that simulates
_readystill pending whentimeDetectorfires to document/lock the ordering assumption.Sent by Cursor Automation: Web compat and sec
|
This PR requires a manual review and approval from a member of one of the following teams:
|
When the platform debug flag is set, detectorPerf dispatches a detectorPerfDebugStats CustomEvent on window after every recorded run, carrying the exact stats snapshot as a JSON-string detail (primitives cross isolated-world boundaries; objects do not on Chromium). Test pages render this as a live per-detector timing overlay for human testing. Production builds never set the flag, so the branch is inert and the page-observability invariant holds for users. Also fixes the integration harness console forwarder to tolerate Playwright console types that don't exist on Node's console (e.g. 'warning'), which the new production-mode test surfaces. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Stale comment
Injected PR Evaluation: Web Compatibility & Security
Re-assessed at
4ac25bc3(adds debug-onlydetectorPerfDebugStatspage broadcast). Prior findings on breakage-report ordering andperformanceNowcapture still apply; new findings below focus on the debug broadcast path.Web Compatibility Assessment
File Lines Severity Finding injected/src/features/detector-perf.js354–366 warning _debugBroadcastdispatches a page-observableCustomEventwhenisDebugis true, partially relaxing the "measurement must never be page-observable" invariant documented at lines 101–105. Integration tests confirm production (debug: false) builds emit nothing, but any platform that setsargs.debugin non-test builds would expose live detector timing to page scripts — including hostile pages that register listeners.injected/src/features/detector-perf.js426–431 info timeDetectorwraps detector calls with twoperformanceNow()reads. Overhead is negligible and synchronous; no return-value or Promise contract changes.injected/src/features/breakage-reporting.js81–91 warning getStatssnapshot relies on fire-and-forgetreportDurationmicrotasks completing beforeawait callFeatureMethod('detectorPerf', 'getStats'). Microtask FIFO ordering makes this work today, but the_ready-promise comment is misleading — this is fragile iftimeDetectorever becomes async or ifcallFeatureMethodgains concurrent dispatch. Integration testbreakage reports carry exact per-detector timing statsprovides coverage.injected/src/features/web-detection.js64–78 info _evaluateMatchnow routes throughtimeDetectorinside existing try/catch; return type and error handling unchanged.injected/src/captured-globals.js20 info performanceNowbindsglobalThis.performance.nowwithout optional chaining (unlikedispatchEvent). Safe at document-start in target WebViews, but inconsistent with neighboring captures.Security Assessment
File Lines Severity Finding injected/src/features/detector-perf.js354–366 warning Debug broadcast uses an unsecreted CustomEventname (detectorPerfDebugStats) dispatched via captureddispatchEvent/CustomEvent. Unlike message-bridge, there is nomessageSecret— acceptable only becauseisDebuggates it, but a platform misconfiguration settingdebug: truein release would let any page script observe per-detector run counts, durations, and severe-crossing attribution. Payload is JSON-stringified (good for isolated-world boundary), but the string is page-readable on Chromium.injected/src/features/detector-perf.js3, 426–431 info Uses captured performanceNow,CustomEvent,dispatchEvent, andhasOwnProperty.Map/Setare instantiated from uncaptured globals — low risk at document-start. NonativeDataleakage; telemetry goes through typedwebEvents.fireEventparams.injected/src/features/detector-perf.js219–221, 735–736 info Input validation on record()rejects malformed names/durations;hasOwnPropertyguards configdetectorOverridesagainst prototype-pollution keys likeconstructor.injected/src/features/breakage-reporting.js89–91 info detectorPerfstats attached to user-initiated breakage reports only; gracefully omitted when feature disabled or returnsCallFeatureMethodError.injected/src/features.js41, 77, 92, 122 info Feature registered on apple/android/windows/extension platform lists — remotely disableable via detectorPerfconfig state.Risk Level
Medium Risk — New telemetry feature with config gating and strong test coverage; no API overrides or messaging-boundary changes. The debug-only page broadcast is the main new surface area, but it is inert in production builds.
Recommendations
- (warning) Add a brief platform-build comment or assertion near
_debugBroadcastdocumenting thatargs.debugmust never be set in release browser builds — the integration test covers the negative path, but native entry-points should be the source of truth.- (warning) Consider making
reportDurationawaitable from the breakage-report path (or snapshot synchronously insidetimeDetectorbefore returning) to remove the implicit microtask-ordering dependency forgetStats.- (info) Align
performanceNowcapture with optional chaining:globalThis.performance?.now?.bind(globalThis.performance)for consistency withdispatchEvent.- (info) Import captured
Map/Setindetector-perf.jsfor consistency with global-capture hygiene.No API shims, message-bridge changes, or
load()-time patches detected. Test coverage is thorough: unit tests for threshold/severe/at-most-once logic, integration tests for disabled state, timeline invisibility, severe attribution, breakage payload, and debug-broadcast gating.Sent by Cursor Automation: Web compat and sec
The feature initializes in every injected subframe, but detectors are top-frame-only by default, so each iframe inflated the pages-measured denominator without ever contributing crossings. Gate the measured emit to the top frame; recording stays frame-agnostic. Co-authored-by: Cursor <cursoragent@cursor.com>
…nvariant - performanceNow captured with optional chaining + Date.now fallback so a missing performance API cannot throw at module load - Import Map/Set from captured-globals for capture consistency - Document the release-builds-must-not-set-debug invariant at _debugBroadcast - Integration test: a second breakage report must show exactly one more run for the standalone detectors, locking the getStats fire-and-forget ordering assumption Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1f03b87. Configure here.
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Stale comment
Injected PR Evaluation: Web Compatibility & Security
Assessed synchronize commit
3372d543("Address detector performance review feedback"). This is a material delta since the prior assessment at1f03b877(merge-only sync).Web Compatibility Assessment
File Lines Severity Finding injected/src/features/detector-perf.js136–140, 450–487 info No browser API overrides, DOM reads, or prototype patches. timeDetectoris a synchronous wrapper around detector call sites; whendetectorPerfis disabled or absent, wrapped detectors behave identically to unwrapped ones.injected/src/features/web-detection.js60–65 info Error handling preserves prior semantics: exceptions from evaluateMatchare caught and returned as'error', whiletimeDetectornow recordsfailedruns before the catch. No change to detector return contracts visible to callers.injected/src/features/breakage-reporting.js80–84 warning getStats()followsrunDetectorsvia fire-and-forgetreportDuration(void+ asynccallFeatureMethod). Microtask FIFO ordering should drainrecord()beforegetStats(), but the explicit ordering-guard integration test was removed in this commit when bot/fraud timing was dropped. Residual risk is telemetry/breakage-report accuracy, not site breakage.injected/src/features/detector-perf.js188–199 info Top-frame-only detectorPerf_measuredguard prevents iframe inflation of the page denominator — correct cross-frame behavior.injected/src/features/breakage-reporting.js60–66 info Removing bot/fraud/YouTube from timeDetectorinstrumentation eliminates extra synchronous work on the breakage-report path — positive for on-demand report latency.injected/src/features/detector-perf.js462–466 info performance.nowis captured at module load; page clock poisoning is acknowledged and mitigated byrecord()input validation (non-finite/negative durations rejected).Security Assessment
File Lines Severity Finding injected/src/captured-globals.js22, 28 info Map/SetandperformanceNow(withDate.nowfallback) added to captured globals — correct hygiene for a feature that must not read page-tampered builtins.injected/src/features/detector-perf.js395–414 warning Debug-only detectorPerfDebugStatsCustomEventis page-observable whenisDebugis set, exposing per-detector timing and severe attribution. The INVARIANT comment (L395–398) correctly documents that production builds must never enableargs.debugon user pages.injected/src/features/detector-perf.js236–243 info hasOwnProperty.callondetectorOverridesprevents prototype-chain keys (e.g.constructor) from affecting threshold resolution.injected/src/features/detector-perf.js34, 87–106 info Periodic event names are bounded to DETECTOR_PERF_DETECTOR_NAMESviagetDetectorPerfEventTypes(); config-driven detector IDs appear only in severe payloads and breakage-report data (not in dynamic event-type strings).injected/src/features/detector-perf.js376–386 info Severe events use explicit { kind, detector, thresholdMs }payloads — nonativeDataspread, no page-controlled event type construction.injected/src/features/detector-perf.js— info Feature is remote-config gated; disabled state is a no-op with graceful callFeatureMethoderror handling. No messaging trust-boundary, origin-validation, orpostMessagechanges.Risk Level
Medium Risk — New config-gated telemetry feature with cross-feature timing instrumentation and minor
captured-globalsadditions, but no API shims, messaging transport changes, or page-world security-boundary relaxations.Recommendations
- Restore a webDetection ordering guard (info): Re-add an integration test that issues two consecutive breakage reports and asserts
detectorPerf.detectors[<id>].runsincrements — the bot/fraud variant was removed in3372d543but the same fire-and-forget pattern still applies to webDetection.- Privacy-config parity (info): Confirm EventHub
sourceentries are updated for the pooledwebDetectionevent namespace (replacing per-detectorbot/fraudtypes) and thatgetDetectorPerfEventTypes()is mirrored in privacy-configuration contract tests.- Debug flag enforcement (info): The
detectorPerfDebugStatsexposure surface is acceptable given the INVARIANT; no code change needed if native release pipelines already guaranteeargs.debugis never set on user pages.Sent by Cursor Automation: Web compat and sec
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Stale comment
Web Compatibility & Security Review — PR #3013
Assessed at:
eb20dd06ac(synchronize, 2026-09-08)
Delta since prior review (3372d543): commiteb20dd06acremovesdetectorPerffromplatformSpecificFeatures, restoring proper remote-config gating.
Web Compatibility Assessment
File Lines Severity Finding injected/src/features/detector-perf.js476–487 info timeDetectoris a synchronous wrapper around detector invocations. No browser API overrides, notoString()masking concerns, and no prototype mutations. WhendetectorPerfis disabled, wrapped calls are behavior-identical to unwrapped ones.injected/src/features/web-detection.js60–65 info Only config-driven webDetectiondetectors are instrumented. Bot/fraud/YouTube timing was intentionally removed, reducing breakage-report latency and avoiding pooled-label ambiguity.injected/src/features/detector-perf.js197–199 info detectorPerf_measuredfires only in the top frame (window.self === window.top), preventing iframe inflation of the page denominator.injected/src/features/breakage-reporting.js80–84 warning getStats()followsawait runDetectors().record()delivery is fire-and-forget viavoid reportDuration(), so microtask ordering determines whether stats include the just-finished report flow. Integration test validates first-report stats; the prior bot/fraud ordering-guard test was removed when those detectors were de-instrumented. Low user impact (telemetry-only), but worth monitoring.injected/integration-test/detector-perf.spec.js188–222 info Confirms no performance.mark/measureentries from measurement path (framework lifecycle marks excluded). Measurement is not page-observable in production.
Security Assessment
File Lines Severity Finding injected/unit-test/utils.js1020–1023 info Resolved since prior review: detectorPerfis correctly absent fromplatformSpecificFeatures. The feature now requires explicit remote-config enablement and can be disabled per-domain — matching the config-gated telemetry pattern.injected/src/features/detector-perf.js3, 22, 28 info Captured globals hygiene: Map,Set,performanceNow,CustomEvent,dispatchEventimported fromcaptured-globals.js.performanceNowuses optional chaining +Date.nowfallback so a missingperformanceAPI cannot throw at module load.injected/src/features/detector-perf.js239 info hasOwnProperty.call()ondetectorOverridesprevents prototype-chain keys likeconstructorfrom being treated as overrides.injected/src/features/detector-perf.js87–106 info getDetectorPerfEventTypes()enumerates a bounded, statically-known event set. Config-driven detector IDs are pooled underwebDetectionfor periodic events; exact IDs appear only in breakage reports and severe-event payloads.injected/src/features/detector-perf.js403–411 warning Debug-only detectorPerfDebugStatsCustomEventexposes timing stats and severe-crossing attribution to any page listener whenisDebugis set. INVARIANT documented at L395–398. Production safety depends on native never settingargs.debugon user pages.injected/src/features/detector-perf.js437–446 info Event dispatch via callFeatureMethod('webEvents', 'fireEvent', …)— no directpostMessage, nonativeDataforwarding, failures silently swallowed.injected/src/captured-globals.js22 info performanceNowadded to captured globals (module-load snapshot).
Risk Level
Medium Risk — New config-gated telemetry feature with captured globals and debug-only page-observable events, but no API overrides, messaging transport changes, or message-bridge modifications.
Recommendations
- Confirm privacy-config EventHub sources are updated for pooled
webDetectionevent types (getDetectorPerfEventTypes()contract) and that staledetectorPerf_*sources are removed.- Verify native release builds never set
args.debugon user pages (sole gate fordetectorPerfDebugStats).- Optional: Add an integration assertion that a second breakage report shows incremented
webDetectionrun counts ingetStats(), replacing the removed bot/fraud ordering-guard test now that onlywebDetectionis instrumented.
Tests: 44/44 unit specs pass in
detector-perf.spec.js.Sent by Cursor Automation: Web compat and sec
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Stale comment
Injected PR Evaluation: Web Compatibility & Security
Delta since prior review (
eb20dd06ac): Commit2914bc4223re-addsdetectorPerftoplatformSpecificFeatures(so telemetry survives user-disabled protections) and introduces an explicit#activegate keyed onhasOwnProperty(featureSettings, 'detectorPerf'). New unit + integration tests cover both the unprotected-domain and absent-from-config paths.
Web Compatibility Assessment
File Lines Severity Finding injected/src/features/detector-perf.jsL490–500 info timeDetectoris a synchronous wrapper around detector invocations; no browser API overrides, no observable page surface in production.injected/src/features/detector-perf.jsL207–209 info detectorPerf_measuredfires only in the top frame, preventing iframe inflation of the page denominator.injected/src/features/breakage-reporting.jsL63–65 info Bot/fraud/YouTube detectors run without timeDetectorinstrumentation, reducing breakage-report latency vs. prior bot/fraud timing.injected/src/features/breakage-reporting.jsL82–84 warning getStatsis awaited after fire-and-forgetreportDurationfrom webDetection; a theoretical ordering race remains for webDetection-only stats. Bot/fraud de-instrumentation mitigates the prior concern.injected/src/features/detector-perf.jsL196–198 info When absent from enabled remote config, init()returns before any events or debug broadcast — platform-specific bundling has no page-observable side effects.Security Assessment
File Lines Severity Finding injected/src/features/detector-perf.jsL145–151, L197–198, L271, L338 info #active+hasOwnPropertyonfeatureSettingscorrectly separates platform-specific bundling from remote-config enablement. Resolves the priorplatformSpecificFeaturesbypass concern from3372d543.injected/src/features/detector-perf.jsL3, L154, L169, L249 info Captured Map/Set/performanceNow/hasOwnProperty;detectorOverridesuses own-property checks to avoid prototype-chain keys likeconstructor.injected/src/features/detector-perf.jsL415–426 warning Debug-only detectorPerfDebugStatsCustomEventis page-observable whenisDebug. Production invariant is documented; verify native never setsargs.debugon user pages.injected/src/features/detector-perf.jsL87–105, L388–389 info Event types are bounded via getDetectorPerfEventTypes(); severe emissions capped bymaxSeverePerPage.injected/src/features/breakage-reporting.jsL82–84 info When inactive, getStats()returnsundefinedwhich still passes theCallFeatureMethodErrorcheck — harmless (JSON.stringifyomitsundefinedvalues) but an explicit null guard would be cleaner.Risk Level
Medium Risk — config-gated internal telemetry using captured globals; no API overrides, messaging boundary changes, or
captured-globals.jsmodifications. Platform-specific bundling is now correctly gated via#active.Recommendations
- Confirm privacy-config EventHub sources cover pooled
webDetectionevent names.- Verify native release builds never set
args.debugon user pages.- (Optional) Add explicit
detectorPerfStats != nullguard inbreakage-reporting.jsbefore attaching stats.- (Optional) Add a webDetection-specific ordering-guard integration test for
getStatsafter asyncrecord.45
detector-perfunit specs pass locally.Sent by Cursor Automation: Web compat and sec
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Stale comment
Injected PR Evaluation: Web Compatibility & Security
Re-assessed at
ebd8f636(sync delta since2914bc4223: ordering regression test +getStatsnull guard + main merge).Web Compatibility Assessment
File Lines Sev Finding injected/src/features/detector-perf.js~490–500 info timeDetectoris a synchronous wrapper around detector evaluation. No browser API overrides; return values and thrown errors are preserved. Overhead is limited to capturedperformanceNowcalls.injected/src/features/web-detection.js~60–65, ~205–206 info _evaluateMatchnow routes throughtimeDetector. Detector semantics unchanged; only timing instrumentation added.injected/src/features/detector-perf.js~415–428 warning _debugBroadcastdispatches a page-observabledetectorPerfDebugStatsCustomEventwhenisDebugis set. Documented INVARIANT requires native to never enable debug on user pages.injected/src/features/breakage-reporting.js~48–49, ~82–84 info getStatsruns afterawait runDetectors, sorecord()microtasks from fire-and-forgetreportDurationcalls drain before the handler continuation reachesgetStats. New integration test (af01d912db) guards second-report run counts.injected/src/features/detector-perf.js~196–208 info #activegate (hasOwnPropertyonfeatureSettings) keeps the feature inert when absent/disabled in remote config despiteplatformSpecificFeaturesbundling. Top-frame-onlymeasuredprevents iframe denominator inflation.Security Assessment
File Lines Sev Finding injected/src/captured-globals.js~20–22 info New performanceNowexport uses.bind(globalThis.performance)withDate.nowfallback — correct capture hygiene.injected/src/features/detector-perf.jsthroughout info Uses captured Map,Set,CustomEvent,dispatchEvent,hasOwnProperty.detectorOverrideslookup uses own-property check (blocks prototype-pollution keys likeconstructor). Event types are bounded/config-derived; nonativeDatain outbound payloads.injected/src/features/detector-perf.js~388–397 info maxSeverePerPagecaps blast radius from misconfigured near-zero thresholds.injected/src/features/detector-perf.js~415–428 warning Debug-only CustomEvent exposes per-detector timing and severe attribution to any page listener when isDebugis true. Acceptable for test builds; production safety depends on the documented native invariant.injected/src/features/breakage-reporting.js~82–84 info detectorPerfStats != nullguard prevents attaching stats when feature is inactive — addresses prior optional recommendation.Risk Level
Medium Risk — config-gated telemetry with captured globals and no API surface overrides; instrumentation wraps existing synchronous detector paths only.
Delta since prior review (
2914bc4223)
af01d912dbadds integration coverage for breakage-reportgetStatsordering (second report run-count assertion).breakage-reporting.jsadds explicitdetectorPerfStats != nullguard.ebd8f636merge-from-main only (CI cache/setup-node); no new injected runtime findings.Recommendations
- Ship-time: confirm privacy-configuration EventHub
sourceentries cover alldetectorPerf_*event types (pooledwebDetectionlabels + threshold edges).- Native contract: verify release builds never set
args.debugon user pages (debug CustomEvent gate).- No further C-S-S changes required for the fire-and-forget ordering concern — microtask ordering + new integration test provide adequate coverage.
45
DetectorPerfunit specs pass locally on Node 24.Sent by Cursor Automation: Web compat and sec
GuiltyDolphin
left a comment
There was a problem hiding this comment.
Thanks @jdorweiler. I did some local testing and can see the events firing.
I think the event names need fixing up - currently just showing webDetection.
Also the fn() measurement is a decent approximation for simple detectors, but many of the suggested detectors that will likely come out of my project won't fit the same measurement pattern (they don't just do one chunk of work; they may do repeated work over a period of time, wait for events, etc.) -- we should make it clear at postmortem that this only handles simple detectors and we'll need to revisit if we want to be able to measure other kinds of detection in future.
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Stale comment
Injected PR Evaluation: Web Compatibility & Security
Sync delta (
0477d4dc): Periodic telemetry now buckets by Web Detection group (adwalls,captcha,autorun, …) instead of a single pooledwebDetectionlabel. Breakage reports and single-run severe events still carry exact config IDs via#detectorsDetailed/ thedetailargument.46
detector-perfunit specs pass locally (Node 24).
Web Compatibility Assessment
File Lines Severity Finding injected/src/features/detector-perf.js488–498 info timeDetectoris a synchronous wrapper: return values and thrown errors are unchanged. Fire-and-forgetrecord()dispatch adds negligible overhead and does not alter detector semantics.injected/src/features/web-detection.js61–66 info Instrumentation passes groupNamefor periodic events andfullDetectorIdasdetailfor exact attribution — no change toevaluateMatchbehaviour or error handling ('error'sentinel preserved).injected/src/features/detector-perf.js205–207 info detectorPerf_measuredgated to top frame only — iframes cannot inflate the page denominator.injected/integration-test/detector-perf.spec.js402–413 info Second-report ordering regression test guards the FIFO microtask assumption between fire-and-forget record()and the awaitedgetStats()call.injected/src/features/detector-perf.js413–427 warning Debug-only detectorPerfDebugStatsCustomEventis page-observable whenisDebugis set. INVARIANT is documented; production builds must never set the debug flag on user pages (integration test coversdebug: false).injected/docs/detector-performance.md7–12 info Bot/fraud/YouTube detectors correctly excluded from instrumentation — avoids misleading cross-trigger comparisons.
Security Assessment
File Lines Severity Finding injected/src/captured-globals.js20–22 info New performanceNowcapture uses optional chaining +Date.nowfallback — avoids module-load throw and page-tamperedperformance.nowreferences.injected/src/features/detector-perf.js3, 152, 167 info Map,Set,hasOwnProperty,CustomEvent,dispatchEventall sourced from captured globals.injected/src/features/detector-perf.js195, 247 info #activegate useshasOwnPropertyonfeatureSettings— bundled feature cannot bypass remote-config disable.detectorOverrideslookup uses own-property check, mitigating prototype-pollution via keys likeconstructor.injected/src/features/detector-perf.js270–271 info NAME_PATTERNrejects malformed group/detail strings before they reach event-type construction — bounds injectable event-name segments.injected/src/features/detector-perf.js386–396 info maxSeverePerPagecap + per-kind guard keys limit blast radius from misconfigured near-zero thresholds.injected/src/features/breakage-reporting.js82–84 info getStatsresult gated on!= nullandCallFeatureMethodError— no attachment when feature is disabled/absent.injected/src/features/detector-perf.js413–427 warning Debug broadcast dispatches a page-listenable CustomEventwith timing/attribution data. Acceptable behindisDebug; residual risk is operational (native must not set debug on user pages).No messaging trust-boundary changes, no
nativeDataleakage, no API overrides, no uncaptured security-sensitive globals in the hot path.
Risk Level
Medium Risk — Config-gated telemetry with proper captured-global hygiene and no browser API shimming; the
0477d4dcgrouping change is a telemetry-contract refinement (not a page-facing behaviour change) but requires coordinated privacy-config EventHub updates.
Recommendations
- High — privacy-config contract: Update EventHub
sourceentries from pooleddetectorPerf_webDetection_*to per-group types (detectorPerf_adwalls_*,detectorPerf_captcha_*, …).getDetectorPerfEventTypes()and docs now assume build-time enumeration of enabled groups.- Medium — operational gate: Confirm native release builds never set
args.debugon user pages.- Low — regression lock: Keep the second-report ordering integration test when touching
callFeatureMethoddispatch semantics.Sent by Cursor Automation: Web compat and sec
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Stale comment
Web Compatibility Assessment
File Lines Severity Finding injected/src/features/detector-perf.js523–534 info timeDetectoris a synchronous wrapper with fire-and-forget reporting. Detector return values and throw semantics are unchanged; measurement cost is excluded from the timed window.injected/src/features/web-detection.js61–66 info Instrumentation passes groupName(periodic telemetry family) andfullDetectorId(exact config ID for severe attribution / breakage reports). No DOM or layout reads added.injected/src/features/detector-perf.js212–214, 360–371 info detectorPerf_measuredfires top-frame only; per-frame accumulators prevent iframe inflation of the page denominator.injected/src/features/detector-perf.jsdocs + code info Bot/fraud (request-driven) and YouTube interference (recurring sweep) detectors are intentionally excluded — their semantics differ from auto-run Web Detection. injected/integration-test/detector-perf.spec.jsordering test info Regression test guards callFeatureMethodmicrotask FIFO sotimeDetectorreporting cannot reorder relative to other feature calls.injected/src/features/detector-perf.js387–409 info New ( d40748b): ConfigurablesingleRunSevereThresholdMs/totalPerPageSevereThresholdMschange which severe edges emit, not detector execution. Combined totals retain highest-edge-only behavior. Highest-first ordering undermaxSeverePerPagepreserves strongest signals.injected/src/features/detector-perf.js448–461 warning Debug-only detectorPerfDebugStatsCustomEventis page-observable whenisDebugis set. Documented INVARIANT: native release builds must never setargs.debugon user pages.No API overrides, prototype patches, or DOM mutations that could break third-party script compatibility.
Security Assessment
File Lines Severity Finding injected/src/features/detector-perf.js3 info Uses captured Map,Set,performanceNow,CustomEvent,dispatchEvent,hasOwnPropertyfromcaptured-globals.js.injected/src/features/detector-perf.js202, 272 info Config gating via hasOwnProperty.call(this.featureSettings, this.name)— disabled/absent feature is inert even when bundled.injected/src/features/detector-perf.js272, 295–296 info hasOwnPropertyondetectorOverridesprevents prototype-chain keys (e.g.constructor) from being treated as overrides.NAME_PATTERNrejects malformed detector/group names.injected/src/features/detector-perf.js233–249 info New ( d40748b): Severe cutoffs require positive finite numbers; invalid values fall back to highest-edge behavior (no crash, no unbounded emission).injected/src/features/detector-perf.js421–430 info New ( d40748b): Dedup guard key now includesthresholdMs;maxSeverePerPage(default 10) caps blast radius for misconfigured thresholds.injected/src/features/detector-perf.js430 info _emitSeverepasses explicit{ kind, detector, thresholdMs }— no object spread of untrusted input; nonativeDataleakage.injected/src/features/detector-perf.js293–296 info record()silently ignores invalid input — recording never throws back into detector call sites.injected/src/features/detector-perf.js448–461 warning Debug CustomEventexposes detector timing and severe attribution to any page listener whenisDebugis true. Sole gate is the platform debug flag.No message-bridge, origin-validation, or captured-globals boundary changes.
Risk Level
Medium Risk — Config-gated internal telemetry with captured globals and input validation; no browser API overrides or messaging trust-boundary changes. The
d40748bsevere-threshold configurability is a telemetry-contract refinement (more granular severe pixels, still capped).Recommendations
- Privacy-config coordination: Per-group periodic event types (
detectorPerf_<group>_…) and any fleet-wide severe-cutoff rollout should stay aligned with EventHub sources in privacy-configuration.- Debug flag invariant: Confirm native release builds never set
args.debugon user pages (the debugCustomEventis the only page-observable surface).- Severe cutoff rollout: When lowering
singleRunSevereThresholdMs/totalPerPageSevereThresholdMsfleet-wide, moredetectorPerf_severepixels can fire per crossing (up tomaxSeverePerPage); validate EventHub/immediate-pixel capacity before broad rollout.- Tests: 50 unit specs in
detector-perf.spec.jspass locally on Node 24 (includes 4 new severe-cutoff cases fromd40748b).Sent by Cursor Automation: Web compat and sec
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Stale comment
Injected PR Evaluation: Web Compatibility & Security
Sync delta (
fe3f40d1sincef015be61): Adds configurablecombinedSevereThresholdMs, aligning combined accumulated severe emissions with the existing single-run and per-group total behavior — every crossed edge at or above the cutoff fires (highest-first, still subject tomaxSeverePerPage). Two unit tests cover the new cutoff and invalid-config fallback. No new API overrides, messaging paths, or detector execution changes.
Web Compatibility Assessment
File Severity Finding injected/src/features/detector-perf.js(timeDetector)info Synchronous wrapper around detector invocations; duration measured before async dispatch so reporting cost does not pollute timing. When detectorPerfis disabled/absent, wrapped calls behave identically to unwrapped ones.injected/src/features/web-detection.jsinfo _evaluateMatchnow passesgroupName+fullDetectorIdintotimeDetector. Detector semantics unchanged; only instrumentation added.injected/src/features/breakage-reporting.jsinfo Bot/fraud/YouTube on-demand detectors intentionally excluded from detectorPerf; only config-drivenwebDetectiondetectors are timed.injected/src/features/detector-perf.js(init)info detectorPerf_measuredfires top-frame only (window.self === window.top) to prevent iframe inflation of page denominator.injected/src/features/detector-perf.js(_checkSevere)info combinedSevereThresholdMs(new in this sync) mirrors single/total severe cutoff semantics; invalid values fall back to highest combined edge. Telemetry-contract change only — no detector execution impact.injected/integration-test/detector-perf.spec.jsinfo Ordering integration test guards microtask FIFO for fire-and-forget callFeatureMethoddispatch.injected/src/features/detector-perf.js(_debugBroadcast)warning Debug-only CustomEvent(detectorPerfDebugStats) exposes timing/stats to page scripts whenisDebugis set. Documented INVARIANT: native release builds must never setargs.debugon user pages.
Security Assessment
File Severity Finding injected/src/captured-globals.jsinfo Adds performanceNowwith optional chaining +Date.nowfallback. Captured at module load before page scripts;.bind()applied correctly.injected/src/features/detector-perf.jsinfo Uses captured Map,Set,CustomEvent,dispatchEvent,hasOwnProperty.NAME_PATTERNvalidates group/detail strings.hasOwnPropertyguards onfeatureSettingsanddetectorOverridesprevent prototype-chain pollution (e.g.constructorkey).injected/src/features/detector-perf.js(record)info Input validation rejects non-finite/negative durations and malformed names; recording never throws back into detector call sites. injected/src/features/detector-perf.js(_emitSevere)info maxSeverePerPagecap (default 10) limits blast radius from misconfigured thresholds. Per-threshold dedup guard key prevents double-emission.injected/src/features/detector-perf.js(init)info Remote-config gating via hasOwnProperty.call(this.featureSettings, this.name)— bundled but inactive when disabled.injected/src/features/detector-perf.js(_dispatch)info Events routed through webEvents.fireEventwith explicit{ type }/{ type, data }— no object spreading, nonativeDataleakage risk.injected/src/features/detector-perf.js(_debugBroadcast)warning Page-observable CustomEventwhen debug flag is set. Sole gate isisDebug; verify native never enables this on production user pages.injected/src/features/breakage-reporting.jsinfo getStatsattached to user-initiated breakage reports only; exact timings keyed by config IDs, not page-derived strings.
Risk Level
Medium Risk — New config-gated telemetry feature with captured globals and no browser API overrides; the
combinedSevereThresholdMscommit is a telemetry-contract refinement within the same bounded surface.
Recommendations
- Privacy-config alignment — Ensure EventHub sources cover all
detectorPerf_*event types including per-group thresholds and the newcombinedSevereThresholdMsrollout path.- Debug flag audit — Confirm native release builds never set
args.debugon user-facing pages (the debugCustomEventis the only page-observable measurement path).- Severe cutoff rollout — When lowering severe cutoffs fleet-wide, validate EventHub immediate-pixel capacity;
maxSeverePerPagecaps per-frame emissions but frequent crossings across pages could increase volume.52 unit specs pass locally (Node 24).
Sent by Cursor Automation: Web compat and sec
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Web Compatibility Assessment
| File | Lines | Severity | Finding |
|---|---|---|---|
injected/src/features/detector-perf.js |
534–545 | info | timeDetector is synchronous, preserves return values, rethrows errors; fire-and-forget reporting does not alter detector semantics. |
injected/src/features/web-detection.js |
68–71 | info | New in fb73f8f9: early exit bypasses the timing wrapper when detectorPerf is absent/disabled — identical to an unwrapped evaluateMatch call, with lower overhead on the default path. Integration tests confirm detectors still run (webDetectionAutoRun count > 0). |
injected/src/features/web-detection.js |
52 | info | Gating via hasOwnProperty on featureSettings matches the detector-perf.js init guard — consistent enable/disable semantics across both features. |
injected/src/features/breakage-reporting.js |
63–65 | info | Bot/fraud/YouTube on-demand detectors intentionally excluded from timing; detection results in breakage reports are unaffected. |
injected/src/features/detector-perf.js |
215–217 | info | detectorPerf_measured is top-frame-only, preventing iframe subframes from inflating the page denominator. |
injected/integration-test/detector-perf.spec.js |
203–214 | info | FIFO ordering regression test guards the microtask race between fire-and-forget record calls and an immediately following getStats. |
injected/src/features/detector-perf.js |
459–469 | warning | Debug detectorPerfDebugStats CustomEvent is page-observable when args.debug is set. Documented invariant: production builds must never enable this flag on user pages. |
Security Assessment
| File | Lines | Severity | Finding |
|---|---|---|---|
injected/src/captured-globals.js |
22, 28 | info | performanceNow, Map, Set captured at load; timeDetector uses captured performanceNow so a page-tampered global cannot throw at the call site. |
injected/src/features/detector-perf.js |
3, 280 | info | Uses captured hasOwnProperty, CustomEvent, dispatchEvent, Map, Set; detectorOverrides keyed with own-property check (blocks prototype-pollution via keys like constructor). |
injected/src/features/detector-perf.js |
31, 303 | info | NAME_PATTERN validates detector/group names before emission or accumulation. |
injected/src/features/detector-perf.js |
205, 302 | info | Remote-config gating via hasOwnProperty on featureSettings; #active guard on all record paths. |
injected/src/features/detector-perf.js |
433–441 | info | maxSeverePerPage cap + per-threshold dedup limits blast radius of a misconfigured threshold push. |
injected/src/features/detector-perf.js |
495–504 | info | Outbound webEvents use explicit {type} / {type, data} — no object spread, no nativeData leakage risk. |
injected/src/features/detector-perf.js |
459–469 | warning | Debug CustomEvent exposes timing/stats to any page listener when debug flag is set; relies on native never setting args.debug on user pages. |
Risk Level
Medium Risk — Config-gated telemetry instrumentation with captured globals; no API overrides or messaging transport changes. Latest commit (fb73f8f9) adds a safe early-exit on the disabled path.
Recommendations
- Privacy-config alignment — Ensure EventHub sources cover per-group event types and configurable severe cutoffs (
singleRunSevereThresholdMs,totalPerPageSevereThresholdMs,combinedSevereThresholdMs). - Debug flag invariant — Verify native release builds never set
args.debugon user pages (debugCustomEventis page-observable). - Fleet rollout — Monitor EventHub capacity if severe cutoffs are lowered fleet-wide.
Sent by Cursor Automation: Web compat and sec
GuiltyDolphin
left a comment
There was a problem hiding this comment.
Thanks for making those changes. Tested the new naming + severity thresholds locally and LGTM.



Asana Task/Github Issue:
Description
Adds a
detectorPerffeature that measures on-device detector execution cost (bot/fraud/adwall utils and config-drivenwebDetectionscans) via a synchronoustimeDetectorwrapper. Per-page counters (runs, worst single run, per-detector total, combined total) drive threshold-crossing events fired at occurrence throughwebEvents(at most once per page per event type), with a cappeddetectorPerf_severeimmediate event when a run crosses the highest configured edge. Exact per-detector stats attach to breakage reports. Threshold edges come from remote config (privacy-configuration#5878); nothing is page-observable in production (noperformance.mark/measure, no DOM reads). A debug-flag-gateddetectorPerfDebugStatsCustomEvent feeds live overlays on the detectorPerf test pages (already live).Testing Steps
Automated (covers all behavior):
The integration tests cover: occurrence events fired once per page, severe with exact detector attribution, breakage-report stats (including the fire-and-forget ordering guard), no page-observable timeline entries, debug broadcast on/off, disabled-state emits nothing, and top-frame-only
measured.Manual end-to-end (macOS debug build, optional):
pr-releases/detector-perf-monitoring, see build-branch comment below) and build the Debug scheme.detectorPerf+ eventHub telemetry enabled: check out privacy-configuration#5878, build it, serve the generated macOS config locally, and set it via Debug → Remote Configuration URL. For deterministic severe events, lowersingleRunThresholdsMsto[0.001]in the served config./usr/bin/log stream --process DuckDuckGo --level debug | grep -i eventhubshows period routing for each event and immediate telemetry for severe.Checklist
Please tick all that apply:
Note
Medium Risk
Adds synchronous timing and telemetry on the Web Detection hot path when config enables the feature; failures are swallowed but misconfigured thresholds could increase event volume until capped.
Overview
Introduces a remote-config-gated
detectorPerffeature that times synchronous Web Detection runs viatimeDetector, emits threshold-crossingdetectorPerf_*events throughwebEvents(including capped immediatedetectorPerf_severe), and attaches exact per-detector stats to breakage reports.webDetectionwrapsevaluateMatchwhendetectorPerfis present in config; on-demand bot/fraud/YouTube detectors are intentionally not instrumented.The feature is registered as a platform-specific dependency (still active when site protections are off) and documented in
detector-performance.md. Tests add a large unit suite, Playwrightdetector-perf.spec.js, and small harness fixes (breakage-reporting waits, Playwrightconsole.warningfallback,performanceNowin captured globals).Reviewed by Cursor Bugbot for commit fb73f8f. Bugbot is set up for automated code reviews on this repo. Configure here.