feat(page-context): collect page-type signals (jsonLdType, ogType, lang) - #2770
Conversation
[Beta] Generated file diffTime updated: Wed, 17 Jun 2026 07:28:03 GMT AndroidFile has changed AppleFile has changed IntegrationFile has changed WindowsFile has changed |
There was a problem hiding this comment.
Stale comment
Injected PR Evaluation: Web Compatibility & Security
Web Compatibility Assessment
File Lines Severity Finding injected/src/features/page-context.js253–265 info Read-only DOM access ( querySelector/querySelectorAllon<head>elements). No API overrides, prototype patches, or descriptor mutations — zerotoString()/instanceoffidelity concerns.injected/src/features/page-context.js275–290 info collectJsonLdTypesonly walks arrays, top-level@type, and@graph. Nested entities (e.g.mainEntity.@type) are intentionally skipped for a cheap signal. Consumers should not assume exhaustive schema.org coverage.injected/src/features/page-context.js593–595, 600–602 info pageTypeSignalsis included in the same content cache as markdown body text. TheMutationObserverwatchesdocument.bodyonly, so head-only updates (late-injected JSON-LD /og:typechanges) may not invalidate the cache until another trigger fires. Typical static<head>metadata is unaffected.injected/src/features/page-context.js724–728 info getPageTypeSignals()runs duringcollectPageContent()ininit()-time collection paths — consistent with existing optional fields (includeHeadings,includeLinks, etc.). Noload()-before-config hazard.injected/unit-test/page-context-signals.spec.jsall info Good unit coverage for malformed JSON, oversized blocks, deduping, and caps. Tests pass locally (13 specs, 0 failures). No warning or error-level web-compat findings.
Security Assessment
File Lines Severity Finding injected/src/features/page-context.js260 info Uses page-reachable JSON.parseinstead of capturedJSONparsefromcaptured-globals.js. A hostile page could substituteJSON.parseafter injection; risk is limited here (parsed output is walked defensively, failures are caught, only@typestrings are retained). Aligning with captured globals would be defense-in-depth consistency, not a blocker.injected/src/features/page-context.js243 info new Set()is uncaptured. TamperedSetcould break deduping or throw; impact is localized to this optional, config-gated sub-field.injected/src/features/page-context.js282–289 info Bracket reads ( obj['@type'],obj['@graph']) onJSON.parseoutput. Prototype-pollutedObject.prototypecould theoretically surface spurious types when own properties are absent; only trimmed strings are collected, limiting blast radius.injected/src/features/page-context.js593–595, 754–757 info New data flows through the existing sendContentResponse→messaging.notify('collectionResult', { serializedPageData })path. No newnativeDatafields, no bridge/origin changes, nopostMessageusage. Appropriately gated behindincludePageTypeSignals(defaultdisabled).injected/src/features/page-context.js256–257, 726–727 info maxBlockLength(default 100k) andmaxTypes(default 10) bound CPU/memory per collection. Remote config can raise these (same pattern asmaxContentLength/upperLimitelsewhere in this feature).No warning, error, or critical security findings.
Risk Level
Low Risk — Config-gated, read-only DOM/metadata extraction with bounded parsing; no API shims, messaging transport changes, or captured-globals modifications.
Recommendations
- (info) When enabling
includePageTypeSignalsin privacy-configuration, add an integration-test case underintegration-test/test-pages/page-context/to verify end-to-end collection (mirrors existing page-context IT coverage).- (info) Consider
import { JSONparse } from '../captured-globals.js'inextractJsonLdTypesfor consistency with hardened features (optional hardening).- (info) Document for consumers that
jsonLdTypeis a shallow@type/@graphharvest, not a full JSON-LD graph walk.- (info) If SPA head updates matter for your use case, consider invalidating cache on
document.headmutations or re-collecting signals outside the body-only observer (not required for initial rollout).Sent by Cursor Automation: Web compat and sec
b89b88d to
3a3341d
Compare
Dismissing stale approval — new commits pushed, awaiting Cursor re-review.
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/apeters/page-type-signals")git submodule (Windows): git -C submodules/content-scope-scripts fetch origin pr-releases/apeters/page-type-signals
git -C submodules/content-scope-scripts checkout origin/pr-releases/apeters/page-type-signalsPin to exact commitnpm (Android / Extension): Swift Package Manager (Apple): .package(url: "https://github.com/duckduckgo/content-scope-scripts.git", revision: "59500c67d0806767e2e44ce9b6a167bdede3c4de")git submodule (Windows): git -C submodules/content-scope-scripts fetch origin pr-releases/apeters/page-type-signals
git -C submodules/content-scope-scripts checkout 59500c67d0806767e2e44ce9b6a167bdede3c4de |
There was a problem hiding this comment.
Stale comment
Injected PR Evaluation: Web Compatibility & Security
Reviewed
extractPageTypeSignalsand its integration intopage-context.js(+ unit tests). This is read-only DOM/metadata extraction behind a new config gate (includePageTypeSignals, defaultdisabled). No API overrides, prototype patches, or messaging transport changes.Web Compatibility Assessment
File Lines Severity Finding injected/src/features/page-context.js253 info querySelectorAll('script[type="application/ld+json"]')is case-sensitive for the attribute value. HTMLtypeis case-insensitive; pages usingAPPLICATION/LD+JSONor mixed case may be missed. Low impact — signals are best-effort.injected/src/features/page-context.js275–290 info collectJsonLdTypesonly walks arrays, top-level@type, and@graph. Nested entities (e.g.mainEntity: { "@type": "Recipe" }) are not traversed. Common on recipe/article pages; won't break sites but may under-collect.injected/src/features/page-context.js275–290 warning Recursive traversal has no depth cap. A page can embed deeply nested JSON-LD (within maxJsonLdBlockLength) that triggersRangeErrorduring collection. Caught byhandleContentCollectionRequest, but whenincludePageTypeSignalsis enabled it can fail the entire content collection for that page.injected/src/features/page-context.js593–595 info Feature is config-gated with default disabledand follows the same optional-field pattern asincludeMetaDescription/includeHeadings. Safe rollout path.injected/unit-test/page-context-signals.spec.jsall info Good unit coverage for happy paths, malformed JSON, caps, and deduping. No integration test for live DOM injection timing (acceptable for this scope). Security Assessment
File Lines Severity Finding injected/src/features/page-context.js260 warning Uses uncaptured JSON.parse.captured-globals.jsexportsJSONparsefor hostile-page hardening. A tamperedJSON.parsecould throw or behave unexpectedly; wrapped in try/catch so impact is limited, but inconsistent with security-sensitive injected patterns.injected/src/features/page-context.js243 warning Uses uncaptured new Set(). CapturedSetis available fromcaptured-globals.js. Low exploitability here, but a tamperedSetcould break dedup logic.injected/src/features/page-context.js282–289 warning Reads obj['@type']andobj['@graph']without own-property checks.Object.prototypepollution with@type/@graphkeys can inject spurious signals into the native payload. PreferhasOwnPropertyfrom captured globals (see broker-protection / api-manipulation patterns).injected/src/features/page-context.js593–595, 754–757 info New data flows through existing serializedPageData: JSON.stringify(content)path. No newnotifyfields, nonativeDataspread, no bridge changes.injected/src/features/page-context.js726–727 info maxPageTypeSignals/maxJsonLdBlockLengthare read from config without upper-bound clamping. Misconfiguration could increase parse work; not a direct exploit but worth capping in code (e.g.Math.min(config, 50)).Risk Level
Medium Risk — New injected-runtime code that reads and parses page-controlled JSON-LD, but no API shims or trust-boundary changes; properly config-gated with sensible defaults and bounds.
Recommendations
- (warning) Import
JSONparseandSetfromcaptured-globals.jsinstead of using page-mutable globals.- (warning) Guard
@type/@graphreads with capturedhasOwnProperty.call(obj, key)before access.- (warning) Add a recursion-depth limit to
collectJsonLdTypes(e.g. 32–64) to prevent stack overflow from adversarial nesting when the feature is enabled.- (info) Consider case-insensitive JSON-LD script discovery (
script[type="application/ld+json" i]where supported, or normalizetypein a manual loop).- (info) Clamp config-driven limits to safe maximums in
getPageTypeSignals().- (info) Ensure
includePageTypeSignals,maxPageTypeSignals, andmaxJsonLdBlockLengthare documented in privacy-configuration before enabling in production.Overall this is a well-scoped, opt-in addition with solid unit tests. The main actionable items are captured-global hygiene, prototype-pollution guards, and recursion bounding before wide rollout.
Sent by Cursor Automation: Web compat and sec
moon0326
left a comment
There was a problem hiding this comment.
I've left one minor comment, but LGTM otherwise.
Address PR #2770 review feedback: - Use `??` (not `||`) for maxPageTypeSignals/maxJsonLdBlockLength so an explicit 0 is honored (@moon0326). - Import captured JSONparse/Set/hasOwnProperty so a hostile page can't subvert parsing or dedup. - Guard @type/@graph reads with hasOwnProperty.call to avoid Object.prototype pollution surfacing spurious types. - Bound JSON-LD recursion (MAX_JSON_LD_DEPTH=32) so adversarial nesting can't fail the whole collection. - Clamp config-driven limits (Math.min) to safe ceilings. - Case-insensitive ld+json script discovery (APPLICATION/LD+JSON). - Add unit specs (depth cap, prototype pollution, casing, explicit 0) and a windows-project integration test (page + config) asserting end-to-end pageTypeSignals. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dismissing stale approval — new commits pushed, awaiting Cursor re-review.
Dismissing stale approval — new commits pushed, awaiting Cursor re-review.
Dismissing stale approval — new commits pushed, awaiting Cursor re-review.
There was a problem hiding this comment.
Stale comment
Web Compatibility & Security Review
Web Compatibility Assessment
File Lines Severity Finding injected/src/features/page-context.js267–330 info Read-only DOM queries ( querySelector/querySelectorAll) with no API overrides or prototype mutation. Matches existing page-context patterns (getMetaDescription,getHeadings, etc.) and is unlikely to break third-party scripts.injected/src/features/page-context.js267 info Case-insensitive attribute selector ( [type="application/ld+json" i]) is already used inweb-compat.jsfor viewport meta tags, so this is an established repo pattern.injected/src/features/page-context.js220–228 info Shallow JSON-LD harvest (top-level + @graphonly) is documented in JSDoc. Consumers must not treatjsonLdTypeas full schema.org graph coverage — this is a product contract note, not a runtime risk.injected/src/features/page-context.js549–557 info MutationObserverwatchesdocument.bodyonly (pre-existing). Head metadata (og:type, JSON-LD scripts,lang) can be stale if injected into<head>after initial collection without a body mutation. Same limitation applies toincludeMetaDescription; not introduced by this PR.injected/integration-test/page-type-signals.spec.js— info Integration test is registered only on the Windows Playwright project. Broader page-context coverage lives in pages.spec.js(chrome-mv3); consider adding a signal assertion there if cross-platform regression signal is desired.No warning or error-level web-compat findings.
Security Assessment
File Lines Severity Finding injected/src/features/page-context.js9, 274, 300–309 info Sensitive operations use captured globals ( JSONparse,Set,hasOwnProperty) rather than re-reading page-tamperable builtins.injected/src/features/page-context.js300–309 info hasOwnProperty.call()guards on@type/@graphprevent pollutedObject.prototypefrom injecting spurious types. Unit test explicitly covers this.injected/src/features/page-context.js14, 255–281, 292–293 info DoS mitigations are solid: maxBlockLengthskip before parse,MAX_JSON_LD_DEPTH(32) bounds recursion,maxTypesshort-circuits iteration. Malformed JSON blocks are skipped without failing collection.injected/src/features/page-context.js613–615 info New functionality is config-gated behind includePageTypeSignals(default disabled), allowing remote rollback without deploy.injected/src/features/page-context.js788–791 info Signals flow through the existing serializedPageData: JSON.stringify(content)notify path. This is page-derived metadata sent to native — same trust model as title/content/headings. No newpostMessageor bridge surface.nativeDatais not included.injected/src/features/page-context.js258 info Non-string @typevalues are filtered, preventing object-coercion surprises in downstream consumers.No warning, error, or critical security findings.
Risk Level
Medium Risk — New runtime signal-extraction logic in injected
page-context, but no API shims, no messaging transport changes, config-gated off by default, and defensive bounds on hostile JSON-LD input.
Recommendations
- (info) Add
includePageTypeSignals,maxPageTypeSignals, andmaxJsonLdBlockLengthto the privacy-configuration schema forpageContextso remote config authors have typed, documented settings.- (info) Ensure downstream consumers understand the shallow JSON-LD scope (documented in code; may warrant a brief note in consumer docs).
- (optional) Extend chrome-mv3
pages.spec.jsPage Context test to assertpageTypeSignalswhen the setting is enabled, for broader platform coverage.Sent by Cursor Automation: Web compat and sec
|
@moon0326 All review comments addressed. |
Dismissing stale approval — new commits pushed, awaiting Cursor re-review.
There was a problem hiding this comment.
Stale comment
Injected PR Evaluation: Web Compatibility & Security
Re-evaluation after
dc38d866— prior warning-level findings are resolved.Web Compatibility Assessment
File Lines Severity Finding injected/src/features/page-context.js264–327 info Read-only DOM access ( querySelector/querySelectorAllon<head>elements). No API overrides, prototype patches, or descriptor mutations — zerotoString()/instanceoffidelity concerns.injected/src/features/page-context.js264 info Case-insensitive script[type="application/ld+json" i]matches the establishedweb-compat.jspattern (meta[name=viewport i]). On engines without Selectors Level 4i,querySelectorAllmay throw; impact is limited becauseincludePageTypeSignalsdefaults todisabledand is remotely gateable.injected/src/features/page-context.js226–228, 289–307 info Shallow @typeharvest (top-level +@graphonly). Now documented in JSDoc — consumers must not assume full schema.org graph coverage.injected/src/features/page-context.js610–611, 617–618 info pageTypeSignalsis cached with body markdown.MutationObserverwatchesdocument.bodyonly, so head-only late injections (JSON-LD /og:type) may not invalidate the cache until another trigger fires. Typical static<head>metadata is unaffected.injected/integration-test/page-type-signals.spec.jsall info End-to-end integration test added; good coverage alongside 17 unit specs. No warning or error-level web-compat findings.
Security Assessment
File Lines Severity Finding injected/src/features/page-context.js9, 252, 271 info Uses captured JSONparse,Set, andhasOwnProperty— addresses prior defense-in-depth feedback. Parsed output is walked defensively; only trimmed string@typevalues are retained.injected/src/features/page-context.js289–307 info hasOwnProperty.callbefore@type/@graphreads mitigatesObject.prototypepollution.MAX_JSON_LD_DEPTH(32) bounds recursion. Oversized blocks skipped viamaxBlockLength.injected/src/features/page-context.js254–260, 266 info recordTypeshort-circuits oncetypes.size >= maxTypes; outer loops also break early. Resolves prior perf concern about wide@graphiteration past the cap.injected/src/features/page-context.js757–759 info clampedNumericSetting()coerces non-finite config to fallback beforeMath.min, preventing NaN from disabling caps. Resolves prior config-hardening warning.injected/src/features/page-context.js610–611 info Gated behind includePageTypeSignals(defaultdisabled) — required remote kill switch present.injected/src/features/page-context.js278 info Array.from(types)uses uncaptured global;Arrayfromis available incaptured-globals.js. TamperedArray.fromcould throw; impact is localized to this optional sub-field. Cosmetic consistency only.injected/src/features/page-context.js785–788 info pageTypeSignalsflows into existingserializedPageData: JSON.stringify(content)notify path. NonativeDataspread; no messaging-boundary changes.No warning, error, or critical security findings.
Risk Level
Low Risk — Config-gated, read-only DOM metadata extraction with defensive bounds, captured globals for hostile-environment parsing, and no API shims or messaging trust-boundary changes.
Recommendations
- (info) Consider wrapping
document.querySelectorAll('script[type="application/ld+json" i]')in try/catch so an unsupported selector on a legacy engine cannot take down the entirecollectPageContent()path when the setting is enabled.- (info) Optional: switch
Array.from(types)→ capturedArrayfrom(types)for consistency with repo hygiene patterns.- (info) If consumers need fresh signals after SPA head mutations, consider extending cache invalidation to
<head>changes (only if product requirements demand it).Sent by Cursor Automation: Web compat and sec
|
@jonathanKingston Thanks, addressed feedback. |
Add an optional pageTypeSignals field to page-context collection, gated behind the includePageTypeSignals feature setting (off by default). It extracts schema.org JSON-LD @type, Open Graph og:type, and the declared page language, riding the existing collectionResult message so consumers (e.g. Duck.ai sidebar shortcuts) get cheap page-type hints with no extra round-trip and no LLM. - Export extractPageTypeSignals(document) for unit testing (mirrors domToMarkdown); JSON-LD parsed defensively (malformed/oversized skipped), @type collected as string or array and from @graph, deduped, casing preserved, capped. - Add jasmine unit specs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address PR #2770 review feedback: - Use `??` (not `||`) for maxPageTypeSignals/maxJsonLdBlockLength so an explicit 0 is honored (@moon0326). - Import captured JSONparse/Set/hasOwnProperty so a hostile page can't subvert parsing or dedup. - Guard @type/@graph reads with hasOwnProperty.call to avoid Object.prototype pollution surfacing spurious types. - Bound JSON-LD recursion (MAX_JSON_LD_DEPTH=32) so adversarial nesting can't fail the whole collection. - Clamp config-driven limits (Math.min) to safe ceilings. - Case-insensitive ld+json script discovery (APPLICATION/LD+JSON). - Add unit specs (depth cap, prototype pollution, casing, explicit 0) and a windows-project integration test (page + config) asserting end-to-end pageTypeSignals. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…passing the cap Malformed config (e.g. "foo") previously produced NaN from Math.min, disabling the type cap. Clamp via a finite-number check that still honors an explicit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e Set Address @jonathanKingston review: a Set already dedupes and preserves insertion order, so it replaces the parallel types array + seen Set. Cap via .size, drop the now-redundant slice (recordType short-circuits at the cap), and rename add -> recordType. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dc38d86 to
911adcf
Compare
Dismissing stale approval — new commits pushed, awaiting Cursor re-review.
There was a problem hiding this comment.
Web Compatibility & Security Review (automation)
Reviewed 911adcff on apeters/page-type-signals. This adds config-gated, read-only page-type signal extraction (jsonLdType, ogType, lang) to the existing page-context feature. No API overrides, messaging transport changes, or prototype patches.
Web Compatibility Assessment
| File | Lines | Severity | Finding |
|---|---|---|---|
injected/src/features/page-context.js |
264–327 | info | Read-only querySelector / querySelectorAll on <script>, <meta>, and <html lang> — same DOM-read pattern as existing page-context collectors. No layout/DOM mutation risk. |
injected/src/features/page-context.js |
264 | info | Case-insensitive script[type="application/ld+json" i] selector improves discovery of non-canonical script types without affecting page scripts. Supported on all DDG WebView targets. |
injected/src/features/page-context.js |
226–228 | info | Shallow @type harvest (top-level + @graph only) is documented. Consumers must not treat jsonLdType as full schema.org graph coverage — acceptable contract limitation. |
injected/src/features/page-context.js |
741–745 | info | Signals are collected from the main document only; same-origin iframe content is not scanned (unlike markdown collection's includeIframes). Intentional scope boundary — document for downstream consumers. |
injected/src/features/page-context.js |
610–612 | info | Feature is opt-in via includePageTypeSignals (default disabled), so no compat impact until remotely enabled per-domain. |
No web-compat warnings or errors identified.
Security Assessment
| File | Lines | Severity | Finding |
|---|---|---|---|
injected/src/features/page-context.js |
9, 271, 252, 297–306 | info | Correct use of captured JSONparse, Set, and hasOwnProperty for hostile-page hardening. Prior review feedback addressed. |
injected/src/features/page-context.js |
14, 268, 256, 290 | info | DoS mitigations are solid: MAX_JSON_LD_DEPTH=32, maxBlockLength (default 100 KB, ceiling 1 MB), maxTypes (default 10, ceiling 50), and per-block parse try/catch so one bad block cannot fail collection. |
injected/src/features/page-context.js |
757–759 | info | clampedNumericSetting() prevents malformed config from producing NaN and bypassing caps — good config-trust hygiene. |
injected/src/features/page-context.js |
278, 291, 299 | info | Array.from / Array.isArray are uncaptured globals. Low practical risk here (internal iteration only), and consistent with broader codebase patterns. Optional harden: import Arrayfrom from captured-globals.js. |
injected/src/features/page-context.js |
610–612, 785–788 | info | pageTypeSignals is added as an explicit field on the content object (not spread from untrusted input). Outbound path reuses existing collectionResult / serializedPageData channel — no new exfiltration surface beyond page-context's existing trust model. Signals are public page metadata only. |
injected/src/features/page-context.js |
297–306 | info | hasOwnProperty.call guards on @type / @graph prevent Object.prototype pollution from surfacing spurious types. Covered by unit test. |
No security warnings, errors, or critical findings.
Risk Level
Low Risk — Config-gated, read-only DOM inspection with bounded JSON parsing; no API shims, messaging changes, or captured-global regressions in critical paths.
Recommendations
- (info) Consider importing
Arrayfromfromcaptured-globals.jsfor consistency with theJSONparse/Sethardening — not blocking. - (info) Ensure privacy-configuration schema documents
includePageTypeSignals,maxPageTypeSignals, andmaxJsonLdBlockLengthwith safe defaults before rollout. - (done) Prior review items (captured globals, depth cap, prototype pollution, numeric clamping, case-insensitive discovery, tests) are addressed in commits
c02ea1541–911adcff3.
Verdict: Approve from a web-compat and security perspective. Well-bounded addition with appropriate hardening and test coverage (17 unit specs + windows integration test).
Sent by Cursor Automation: Web compat and sec


Add an optional pageTypeSignals field to page-context collection, gated behind the includePageTypeSignals feature setting (off by default). It extracts schema.org JSON-LD @type, Open Graph og:type, and the declared page language, riding the existing collectionResult message so consumers (e.g. Duck.ai sidebar shortcuts) get cheap page-type hints with no extra round-trip and no LLM.
Asana Task/Github Issue: https://app.asana.com/1/137249556945/project/1209072953775241/task/1215655051685860
Description
Adds page signal collection to the page context collection script.
Testing Steps
Added unit tests
Checklist
Please tick all that apply:
Note
Low Risk
Feature is config-gated off by default and only adds best-effort metadata to existing collection; JSON-LD handling includes explicit bounds to avoid breaking full page collection.
Overview
Adds optional
pageTypeSignalsto page-contextcollectionResultpayload whenincludePageTypeSignalsis enabled (default off), giving consumers cheap page-type hints without an extra round-trip.New
extractPageTypeSignalsharvests JSON-LD@type(string/array,@graph, deduped, order preserved),og:type, andhtml lang. JSON-LD parsing is defensive: malformed/oversized blocks skipped, type count and block size capped via config (maxPageTypeSignals,maxJsonLdBlockLength), recursion bounded, case-insensitive script type,hasOwnPropertyfor@type/@graph.Coverage includes JSDOM unit specs, a Playwright end-to-end spec with fixture HTML/config, and registration of that spec in the Windows Playwright project.
Reviewed by Cursor Bugbot for commit 911adcf. Bugbot is set up for automated code reviews on this repo. Configure here.