Skip to content

Fall back to ErrorEvent.message in special-pages error handlers#2714

Open
noisysocks wants to merge 2 commits into
mainfrom
randerson/special-pages-error-handler-fallback
Open

Fall back to ErrorEvent.message in special-pages error handlers#2714
noisysocks wants to merge 2 commits into
mainfrom
randerson/special-pages-error-handler-fallback

Conversation

@noisysocks
Copy link
Copy Markdown
Contributor

@noisysocks noisysocks commented May 25, 2026

Asana Task/Github Issue: https://app.asana.com/1/137249556945/project/1207340338530322/task/1215001863923847

Description

The window.addEventListener('error', …) handlers in onboarding, new-tab, and history all read event.error?.message and fell through to 'unknown error' when event.error was null. That's the case for cross-origin script errors, some resource load failures, and throw undefinedErrorEvent.message still has useful info (e.g. "Script error.") in those cases. The old code discarded it.

This produced the m_mac_onboarding_exception-reported spike on macOS 1.190.0 (message=[uncaught] unknown error, empty id) — the pixel landed in native but the JS side already lost everything diagnostic.

Fix: event.error?.message || event.message || 'unknown error'. Same change applied to all three special pages so they don't drift.

While there, simplify the unhandledrejection handler to event.reason?.message || String(event.reason) — same shape, and Promise.reject(undefined / null / 0) now report distinct messages (undefined, null, 0) instead of all collapsing to 'unknown rejection'.

Testing Steps

  1. Open any of the three pages in a browser (swap new-tab for onboarding or history):

  2. Open devtools console.

  3. Paste this snippet and hit enter — it captures the native-bound notifications, fires every error/rejection shape we care about, and prints what the JS would send to native:

    window.__playwright_01 = { mocks: { outgoing: [] }, subscriptions: new Map() };
    const cases = [
      ['throw new Error',          () => setTimeout(() => { throw new Error('real error'); }, 0)],
      ['cross-origin shape (bug)', () => window.dispatchEvent(new ErrorEvent('error', { message: 'Script error.', filename: 'https://cdn.example/x.js', lineno: 42, colno: 7, error: null }))],
      ['truly empty error',        () => window.dispatchEvent(new ErrorEvent('error', { error: null }))],
      ['throw "raw string"',       () => setTimeout(() => { throw 'raw string'; }, 0)],
      ['Promise.reject(Error)',    () => setTimeout(() => Promise.reject(new Error('rejection')), 0)],
      ['Promise.reject(undefined)',() => setTimeout(() => Promise.reject(undefined), 0)],
      ['Promise.reject(null)',     () => setTimeout(() => Promise.reject(null), 0)],
      ['Promise.reject(0)',        () => setTimeout(() => Promise.reject(0), 0)],
    ];
    (async () => {
      for (const [label, fn] of cases) {
        window.__playwright_01.mocks.outgoing.length = 0;
        try { fn(); } catch {}
        await new Promise(r => setTimeout(r, 200));
        const sent = window.__playwright_01.mocks.outgoing
          .map(c => typeof c.payload === 'string' ? c.payload : c.payload?.params?.message)
          .filter(m => typeof m === 'string' && (m.startsWith('[uncaught]') || m.startsWith('[unhandledrejection]')));
        console.log(label.padEnd(28), '→', sent.join(' | ') || '(nothing)');
      }
    })();
  4. You should see (the second line is the actual production bug being fixed):

    throw new Error             → [uncaught] real error
    cross-origin shape (bug)    → [uncaught] Script error.
    truly empty error           → [uncaught] unknown error
    throw "raw string"          → [uncaught] Uncaught raw string
    Promise.reject(Error)       → [unhandledrejection] rejection
    Promise.reject(undefined)   → [unhandledrejection] undefined
    Promise.reject(null)        → [unhandledrejection] null
    Promise.reject(0)           → [unhandledrejection] 0
    

    Before this PR, the "cross-origin shape" line would have read [uncaught] unknown error, and the three falsy Promise.reject lines would all have read [unhandledrejection] unknown rejection.

Checklist

Please tick all that apply:

  • I have tested this change locally
  • I have tested this change locally in all supported browsers
  • This change will be visible to users
  • I have added automated tests that cover this change
  • I have ensured the change is gated by config
  • This change was covered by a ship review
  • This change was covered by a tech design
  • Any dependent config has been merged

Note

Low Risk
Telemetry-only message formatting in page bootstrap listeners; no auth, data, or user-facing behavior changes beyond clearer exception text sent to native.

Overview
Special-pages onboarding, new-tab, and history now build uncaught error reports from event.error?.message, then event.message, then a fallback—so cases like cross-origin ErrorEvent shapes (error: null, message still set) no longer collapse to [uncaught] unknown error.

Unhandled rejection reporting uses event.reason?.message || String(event.reason) so non-Error reasons (e.g. undefined, null, 0) send distinct native messages instead of a generic unknown string.

Onboarding integration tests cover the ErrorEvent.message fallback and those rejection shapes.

Reviewed by Cursor Bugbot for commit ca9c960. Bugbot is set up for automated code reviews on this repo. Configure here.

Cross-origin script errors fire with event.error=null but useful info
in event.message. The old handler discarded that and reported
'[uncaught] unknown error', which is what produced the
m_mac_onboarding_exception-reported spike on macOS 1.190.0.

Also simplify the rejection handler so falsy reasons (undefined, null,
0) report distinct messages instead of all bucketing to 'unknown'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 25, 2026

Build Branch

Branch pr-releases/randerson/special-pages-error-handler-fallback
Commit 0870971e5b
Updated May 25, 2026 at 9:44:55 AM UTC

Static preview entry points

QR codes (mobile preview)
Entry point QR code
Docs QR for docs preview
Static pages QR for static pages preview
Integration pages QR for integration pages preview

Integration commands

npm (Android / Extension):

npm i github:duckduckgo/content-scope-scripts#pr-releases/randerson/special-pages-error-handler-fallback

Swift Package Manager (Apple):

.package(url: "https://github.com/duckduckgo/content-scope-scripts.git", branch: "pr-releases/randerson/special-pages-error-handler-fallback")

git submodule (Windows):

git -C submodules/content-scope-scripts fetch origin pr-releases/randerson/special-pages-error-handler-fallback
git -C submodules/content-scope-scripts checkout origin/pr-releases/randerson/special-pages-error-handler-fallback
Pin to exact commit

npm (Android / Extension):

npm i github:duckduckgo/content-scope-scripts#0870971e5b01e61cbd2dd2c569b82d1cad85b89a

Swift Package Manager (Apple):

.package(url: "https://github.com/duckduckgo/content-scope-scripts.git", revision: "0870971e5b01e61cbd2dd2c569b82d1cad85b89a")

git submodule (Windows):

git -C submodules/content-scope-scripts fetch origin pr-releases/randerson/special-pages-error-handler-fallback
git -C submodules/content-scope-scripts checkout 0870971e5b01e61cbd2dd2c569b82d1cad85b89a

@github-actions github-actions Bot added the semver-patch Bug fix / internal — no release needed label May 25, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 25, 2026

[Beta] Generated file diff

Time updated: Mon, 25 May 2026 09:45:43 GMT

Apple
    - apple/pages/history/dist/index.js
  • apple/pages/new-tab/dist/index.js
  • apple/pages/onboarding/dist/index.js

File has changed

Integration
    - integration/pages/history/dist/index.js
  • integration/pages/new-tab/dist/index.js
  • integration/pages/onboarding/dist/index.js

File has changed

Windows
    - windows/pages/history/dist/index.js
  • windows/pages/new-tab/dist/index.js
  • windows/pages/onboarding/dist/index.js

File has changed

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Web Compatibility Assessment

No web-compat findings.

  • special-pages/pages/history/src/index.js:86-93, special-pages/pages/new-tab/src/index.js:113-120, special-pages/pages/onboarding/app/index.js:40-47 - info: the changed listeners only affect diagnostic reporting for special pages. They do not wrap/shim browser APIs, alter prototypes/descriptors, mutate the DOM, or change injected page-world behavior.
  • Same ranges - info: existing integration tests cover throw new Error(...) and Promise.reject(new Error(...)); the new ErrorEvent.message and primitive rejection fallback paths are manually verified but not covered by automated tests.

Security Assessment

No security findings.

  • Same ranges - info: the outgoing payload remains a literal reportInitException message; there is no nativeData spreading, message bridge change, origin validation change, new network request, postMessage, or privileged data exposure. This is special-page app code, not hostile page-world injected code.

Risk Level

Low Risk - limited to three special-page global error/rejection diagnostic handlers, with no injected runtime, wrapper utility, captured-global, or messaging transport changes.

Recommendations

  • Non-blocking: add explicit integration coverage for new ErrorEvent('error', { message: 'Script error.', error: null }) and primitive/nullish rejection reasons (undefined, null, 0) in the existing global error listener suites so the new fallback behavior does not regress.
Open in Web View Automation 

Sent by Cursor Automation: Web compat and sec

daxtheduck
daxtheduck previously approved these changes May 25, 2026
@noisysocks noisysocks requested a review from shakyShane May 25, 2026 02:01
Copy link
Copy Markdown
Contributor

@shakyShane shakyShane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's provable in a console snippet, could you add a playwright test to at least one of these?

@daxtheduck daxtheduck dismissed their stale review May 25, 2026 09:37

Dismissing stale approval — new commits pushed, awaiting Cursor re-review.

daxtheduck
daxtheduck previously approved these changes May 25, 2026
@noisysocks noisysocks force-pushed the randerson/special-pages-error-handler-fallback branch from d208bc7 to 751431f Compare May 25, 2026 09:41
@daxtheduck daxtheduck dismissed their stale review May 25, 2026 09:41

Dismissing stale approval — new commits pushed, awaiting Cursor re-review.

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Web Compatibility Assessment

No web-compat findings.

  • special-pages/pages/history/src/index.js lines 86-93, special-pages/pages/new-tab/src/index.js lines 113-120, special-pages/pages/onboarding/app/index.js lines 40-47 - info: the changed global error/rejection listeners only affect diagnostic reporting in special pages. They do not wrap or shim browser APIs, alter prototypes/descriptors, mutate page DOM, or change injected page-world behavior.
  • special-pages/pages/onboarding/integration-tests/onboarding.v4.spec.js lines 855-891 - info: the previously missing fallback coverage is now present for cross-origin-style ErrorEvent objects with error: null and for primitive/nullish rejection reasons.

Security Assessment

No security findings.

  • Same handler ranges - info: the outgoing reportInitException payload remains a literal message object/string and does not spread untrusted objects, include nativeData, add postMessage, change origin validation, or touch the message bridge. The template-literal callsites preserve the message: string contract before data crosses the special-page native messaging boundary.

Risk Level

Low Risk - limited to special-page diagnostic fallback logic and targeted Playwright coverage, with no injected runtime, wrapper utility, captured-global, or messaging transport changes.

Recommendations

No blocking recommendations.

Validation: npm run test-int -- pages/onboarding/integration-tests/onboarding.v4.spec.js --grep "global error listeners" --reporter list passed (10 passed).

Open in Web View Automation 

Sent by Cursor Automation: Web compat and sec

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Web Compatibility Assessment

No web-compat findings.

  • special-pages/pages/history/src/index.js:86-93, special-pages/pages/new-tab/src/index.js:113-120, special-pages/pages/onboarding/app/index.js:40-47 - info: the changed listeners only affect diagnostic error reporting in special pages. They do not wrap/shim browser APIs, alter prototypes/descriptors, mutate DOM structure, or affect injected page-world behavior.
  • special-pages/pages/onboarding/integration-tests/onboarding.v4.spec.js:855-889 - info: the synchronized update adds automated coverage for the previously noted ErrorEvent.message fallback and primitive/nullish rejection reasons.

Security Assessment

No security findings.

  • Same handler ranges - info: outgoing messages remain literal reportInitException payloads with only a stringified message; there is no nativeData spreading, message bridge change, origin-validation relaxation, new network request, postMessage, or privileged data exposure.

Risk Level

Low Risk - limited to special-page diagnostic handlers and targeted onboarding integration tests, with no injected runtime, wrapper utility, captured-global, or messaging transport changes.

Recommendations

No blocking recommendations. Targeted validation passed: npm run test-int -- --grep "global error listeners" --reporter list from special-pages/ (16 passed).

Open in Web View Automation 

Sent by Cursor Automation: Web compat and sec

Covers the cross-origin ErrorEvent shape (error=null) that produced
the production pixel, and the primitive/nullish rejection reasons
(undefined, null, 0) that previously all bucketed to 'unknown rejection'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@noisysocks noisysocks force-pushed the randerson/special-pages-error-handler-fallback branch from 751431f to ca9c960 Compare May 25, 2026 09:44
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Cursor review was not successful.

This PR requires a manual review and approval from a member of one of the following teams:

  • @duckduckgo/content-scope-scripts-owners
  • @duckduckgo/apple-devs
  • @duckduckgo/android-devs
  • @duckduckgo/team-windows-development
  • @duckduckgo/extension-owners
  • @duckduckgo/config-aor
  • @duckduckgo/breakage-aor
  • @duckduckgo/breakage

@noisysocks noisysocks requested a review from shakyShane May 25, 2026 09:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

semver-patch Bug fix / internal — no release needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants