{"record":{"id":"fe5e1d1a48ef2372","repo":"nextcloud/server","slug":"unable-to-open-a-new-tab-for-printing","errorCode":null,"errorMessage":"Unable to open a new tab for printing","messagePattern":"Unable to open a new tab for printing","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/twofactor_backupcodes/src/service/PrintService.ts","lineNumber":21,"sourceCode":" * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { getCapabilities } from '@nextcloud/capabilities'\nimport { showError } from '@nextcloud/dialogs'\nimport { t } from '@nextcloud/l10n'\n\n/**\n * Open a new tab and print the given backup codes\n *\n * @param data - The backup codes to print\n */\nexport function print(data: string[]): void {\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tconst name = (getCapabilities() as any).theming.name || 'Nextcloud'\n\tconst newTab = window.open('', t('twofactor_backupcodes', '{name} backup codes', { name }))\n\tif (!newTab) {\n\t\tshowError(t('twofactor_backupcodes', 'Unable to open a new tab for printing'))\n\t\tthrow new Error('Unable to open a new tab for printing')\n\t}\n\n\tconst heading = newTab.document.createElement('h1')\n\theading.textContent = t('twofactor_backupcodes', '{name} backup codes', { name })\n\tconst pre = newTab.document.createElement('pre')\n\tfor (const code of data) {\n\t\tconst codeLine = newTab.document.createTextNode(code)\n\t\tpre.appendChild(codeLine)\n\t\tpre.appendChild(newTab.document.createElement('br'))\n\t}\n\n\tnewTab.document.body.innerHTML = ''\n\tnewTab.document.body.appendChild(heading)\n\tnewTab.document.body.appendChild(pre)\n\n\tnewTab.print()\n\tnewTab.close()\n}","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/nextcloud/server/blob/ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3/apps/twofactor_backupcodes/src/service/PrintService.ts#L3-L39","documentation":"Thrown by print() in apps/twofactor_backupcodes/src/service/PrintService.ts when window.open() returns null, i.e. the browser refused to open the new tab needed to render the backup codes for printing. Browsers return null (rather than throwing) when a popup blocker or embedding policy blocks the call; the service already shows a user-facing error via showError before rethrowing.","triggerScenarios":"window.open('', ...) returning null: a popup blocker extension/native blocker active, the call happening outside a direct user gesture (any await between the click and the call), the page running in an embedded webview (in-app browser) that forbids popups, or strict enterprise browser policies.","commonSituations":"User clicks 'Print codes' but an async fetch of the codes happens first, breaking the user-gesture chain; mobile in-app browsers (iOS Safari views, embedded Electron) blocking window.open; users with aggressive ad-blockers; kiosk-mode browsers.","solutions":["Invoke print() synchronously inside the click handler — fetch the codes before showing the button, so no await sits between gesture and window.open","Add a fallback that renders the codes in the current page (hidden iframe or a print-only element) when window.open returns null","Tell the user to allow popups for this site (the showError message already hints at the failure)","Offer alternatives that do not need popups: download as file or copy-to-clipboard"],"exampleFix":"// before\nasync function onPrintClick() {\n\tconst codes = await fetchCodes() // async gap breaks user gesture -> popup blocked\n\tprint(codes)\n}\n\n// after\nfunction onPrintClick() {\n\tprint(codes) // codes loaded up-front; window.open runs in the gesture\n}\n// plus iframe fallback:\nfunction printFallback(codes: string[]) {\n\tconst iframe = document.createElement('iframe')\n\tiframe.style.display = 'none'\n\tiframe.srcdoc = `<pre>${codes.join('<br>')}</pre>`\n\tiframe.onload = () => { iframe.contentWindow?.print(); iframe.remove() }\n\tdocument.body.appendChild(iframe)\n}","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n\tprint(codes)\n} catch (error) {\n\tif (error instanceof Error && error.message.includes('Unable to open a new tab')) {\n\t\tprintFallbackViaIframe(codes) // hidden-iframe print needs no popup\n\t} else throw error\n}","preventionTips":["Never put an await between the user's click and window.open — load the data first, then render the button","Offer popup-free alternatives: download the codes or copy to clipboard","In embedded webviews, prefer iframe printing over window.open entirely"],"tags":["popup","print","browser","window-open","twofactor"],"backgroundTag":"popup-blocked","analyzedSha":"ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3","analyzedAt":"2026-08-17T01:36:13.386Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}