FileSystem API触发SecurityError:批量图片转PDF保存异常排查
问题根源
你遇到的SecurityError是浏览器安全机制导致的:showSaveFilePicker必须在用户主动触发的手势上下文(比如点击、按键)的有效期内调用。当处理超过5张图片时,生成PDF的耗时太长,超出了浏览器允许的用户手势生命周期,等到执行savePDFFile时,已经脱离了用户手势的触发范围,所以触发了安全限制。
5张图片能正常工作,只是因为处理耗时短,还没超出手势有效期而已——这不是可靠的阈值,换性能差的设备,可能3张就会出问题。
解决方案
核心思路是先获取文件保存句柄,再处理PDF生成:把showSaveFilePicker的调用移到点击事件的最开头(用户手势刚触发、还没开始处理图片的时候),确保它在手势有效期内执行。之后再处理图片转换、PDF生成,最后用提前拿到的句柄写入文件。
修改后的代码示例
mainContainer.addEventListener('click', async function (event) { if (event.target.id === 'dl-button') { // 第一步:先在用户手势有效期内获取文件保存句柄 let fileHandle; try { fileHandle = await window.showSaveFilePicker({ suggestedName: 'images.pdf', types: [{ description: 'PDF document from captured slides', accept: {'application/pdf': ['.pdf']} }], }); } catch (err) { alert(err); return; // 用户取消选择或出错,直接终止流程 } // 第二步:再处理图片和PDF生成 const selectedImgs = getSelectedImages(); let images = await getAllImageDataUrls(selectedImgs); await triggerImagesDownload(images, fileHandle); } }); async function embedImage(pdfDoc, base64Image) { let base64ImagePart = base64Image.split(';base64,')[1] const isJPEG = base64ImagePart.startsWith("/9j/") const imageBytes = Uint8Array.from(atob(base64ImagePart), c => c.charCodeAt(0)); if (isJPEG) { return await pdfDoc.embedJpg(imageBytes); } else { return await pdfDoc.embedPng(imageBytes); } } // 修改triggerImagesDownload,接收提前获取的fileHandle async function triggerImagesDownload(base64Images, fileHandle) { // Create a new PDF document const pdfDoc = await PDFLib.PDFDocument.create(); const embedImagesPromises = base64Images.map(base64Image => embedImage(pdfDoc, base64Image)); const images = await Promise.all(embedImagesPromises); images.forEach(image => { const page = pdfDoc.addPage([image.width, image.height]); page.drawImage(image, { x: 0, y: 0, width: image.width, height: image.height, }); }); // Serialize the PDFDocument to bytes const pdfBytes = await pdfDoc.save(); // 直接用提前拿到的fileHandle写入,不用再调用showSaveFilePicker await savePDFFile(new Blob([pdfBytes], {type: 'application/pdf'}), fileHandle); chrome.storage.local.set({currentView: "home"}, function() { window.location.reload(); }); } // 修改savePDFFile,接收传入的fileHandle async function savePDFFile(pdfBlob, fileHandle) { try { const writableStream = await fileHandle.createWritable(); await writableStream.write(pdfBlob); await writableStream.close(); console.log('PDF saved successfully.'); } catch (err) { alert(err) } }
补充说明
浏览器对用户手势的有效期没有公开的固定时长,它基于操作连续性判断——只要异步操作链没被太长耗时打断,可能还能保留上下文,但一旦耗时超过隐性阈值,就会被判定为脱离用户手势。提前获取文件句柄是最可靠的方案,完全规避了这个限制。
内容的提问来源于stack exchange,提问作者Edoardo Balducci
相关产品推荐
相关产品推荐

