nextcloud/server · error · Error

Unable to open a new tab for printing

Error message

Unable to open a new tab for printing

What it means

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.

Source

Thrown at apps/twofactor_backupcodes/src/service/PrintService.ts:21

 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

import { getCapabilities } from '@nextcloud/capabilities'
import { showError } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'

/**
 * Open a new tab and print the given backup codes
 *
 * @param data - The backup codes to print
 */
export function print(data: string[]): void {
	// eslint-disable-next-line @typescript-eslint/no-explicit-any
	const name = (getCapabilities() as any).theming.name || 'Nextcloud'
	const newTab = window.open('', t('twofactor_backupcodes', '{name} backup codes', { name }))
	if (!newTab) {
		showError(t('twofactor_backupcodes', 'Unable to open a new tab for printing'))
		throw new Error('Unable to open a new tab for printing')
	}

	const heading = newTab.document.createElement('h1')
	heading.textContent = t('twofactor_backupcodes', '{name} backup codes', { name })
	const pre = newTab.document.createElement('pre')
	for (const code of data) {
		const codeLine = newTab.document.createTextNode(code)
		pre.appendChild(codeLine)
		pre.appendChild(newTab.document.createElement('br'))
	}

	newTab.document.body.innerHTML = ''
	newTab.document.body.appendChild(heading)
	newTab.document.body.appendChild(pre)

	newTab.print()
	newTab.close()
}

View on GitHub (pinned to ecdeb153ff)

Solutions

  1. Invoke print() synchronously inside the click handler — fetch the codes before showing the button, so no await sits between gesture and window.open
  2. Add a fallback that renders the codes in the current page (hidden iframe or a print-only element) when window.open returns null
  3. Tell the user to allow popups for this site (the showError message already hints at the failure)
  4. Offer alternatives that do not need popups: download as file or copy-to-clipboard

Example fix

// before
async function onPrintClick() {
	const codes = await fetchCodes() // async gap breaks user gesture -> popup blocked
	print(codes)
}

// after
function onPrintClick() {
	print(codes) // codes loaded up-front; window.open runs in the gesture
}
// plus iframe fallback:
function printFallback(codes: string[]) {
	const iframe = document.createElement('iframe')
	iframe.style.display = 'none'
	iframe.srcdoc = `<pre>${codes.join('<br>')}</pre>`
	iframe.onload = () => { iframe.contentWindow?.print(); iframe.remove() }
	document.body.appendChild(iframe)
}
Defensive patterns

Strategy: fallback

Try / catch

try {
	print(codes)
} catch (error) {
	if (error instanceof Error && error.message.includes('Unable to open a new tab')) {
		printFallbackViaIframe(codes) // hidden-iframe print needs no popup
	} else throw error
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.


AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17). Data as JSON: /api/errors/fe5e1d1a48ef2372. Report an issue: GitHub.