diff --git a/dev-packages/browser-integration-tests/suites/integrations/globalHandlers/dataUrls/subject.js b/dev-packages/browser-integration-tests/suites/integrations/globalHandlers/dataUrls/subject.js new file mode 100644 index 000000000000..0c22e79964df --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/globalHandlers/dataUrls/subject.js @@ -0,0 +1,11 @@ +const workerCode = ` + self.addEventListener('message', (event) => { + if (event.data.type === 'error') { + throw new Error('Error thrown in worker'); + } + }); +`; + +const worker = new Worker(`data:text/javascript;base64,${btoa(workerCode)}`); + +worker.postMessage({ type: 'error' }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/globalHandlers/dataUrls/test.ts b/dev-packages/browser-integration-tests/suites/integrations/globalHandlers/dataUrls/test.ts new file mode 100644 index 000000000000..e890ea128069 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/globalHandlers/dataUrls/test.ts @@ -0,0 +1,41 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, waitForErrorRequest } from '../../../../utils/helpers'; + +/** + * Tests a special case where the `globalHandlersIntegration` itself creates a stack frame instead of using + * stack parsers. This is necessary because we don't always get an `error` object passed to `window.onerror`. + * @see `globalhandlers.ts#_enhanceEventWithInitialFrame` + */ +sentryTest('detects and handles data urls on first stack frame', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + + const errorEventPromise = waitForErrorRequest(page, e => { + return !!e.exception?.values; + }); + + await page.goto(url); + + const errorEvent = envelopeRequestParser(await errorEventPromise); + + expect(errorEvent?.exception?.values?.[0]).toEqual({ + mechanism: { + handled: false, + synthetic: true, + type: 'auto.browser.global_handlers.onerror', + }, + stacktrace: { + frames: [ + { + colno: expect.any(Number), // webkit reports different colno than chromium + filename: '', + function: '?', + in_app: true, + lineno: 4, + }, + ], + }, + type: 'Error', + value: expect.stringMatching(/(Uncaught )?Error: Error thrown in worker/), // webikt throws without "Uncaught " + }); +}); diff --git a/packages/browser/src/integrations/globalhandlers.ts b/packages/browser/src/integrations/globalhandlers.ts index ebfdff6d2f58..2aa29731a9b0 100644 --- a/packages/browser/src/integrations/globalhandlers.ts +++ b/packages/browser/src/integrations/globalhandlers.ts @@ -171,7 +171,7 @@ function _enhanceEventWithInitialFrame( const colno = column; const lineno = line; - const filename = isString(url) && url.length > 0 ? url : getLocationHref(); + const filename = getFilenameFromUrl(url) ?? getLocationHref(); // event.exception.values[0].stacktrace.frames if (ev0sf.length === 0) { @@ -199,3 +199,20 @@ function getOptions(): { stackParser: StackParser; attachStacktrace?: boolean } }; return options; } + +function getFilenameFromUrl(url: string | undefined): string | undefined { + if (!isString(url) || url.length === 0) { + return undefined; + } + + // stack frame urls can be data urls, for example when initializing a Worker with a base64 encoded script + // in this case we just show the data prefix and mime type to avoid too long raw data urls + if (url.startsWith('data:')) { + const match = url.match(/^data:([^;]+)/); + const mimeType = match ? match[1] : 'text/javascript'; + const isBase64 = url.includes('base64,'); + return ``; + } + + return url.slice(0, 1024); +}