1. 引言
Web 打印 API 为在隔离上下文中实现打印 功能带来了前所未有的灵活性。该 API 不会暴露在普通 Web 上下文中。
1.1. 现有替代方案
目前存在一个
window.
方法,但它基本上只是
打开一个打印对话框,并要求用户完成剩余操作。Web 打印 API
使应用开发者可以从隔离上下文中的 Web 应用直接访问操作系统本地可用的打印机,
并允许使用自定义
打印属性(例如纸张大小、颜色设置、质量等)提交打印作业。
print()
1.2. Web 打印 API 功能
使用 Web 打印 API,你可以:
-
直接从代码提交打印作业,无需任何对话框!
-
实现你自己的打印对话框!
-
使用自定义打印属性进行打印,无需任何用户交互!
-
列出打印机,并获取其打印机能力以及 打印机状态(空闲、忙碌等)。 在了解打印机能力后,应用可以准备具有特定打印属性的 可打印文件。
-
以任意方式自动化打印。构建以前重复性的打印 工作流。
-
除此之外,与
window.不同的是,你可以观察自己打印作业的作业状态——它们是否完成、 失败等。print()
RFC8011 第 5 节对于进一步了解尤其有帮助 (https://datatracker.ietf.org/doc/html/rfc8011#section-5)。
2. 示例
2.1. 列出打印机和基本属性
try { const printers= await printing. getPrinters(); for ( const printerof printers) { const attributes= printer. cachedAttributes(); console. log( $attributes { printerName . 具有以下 } 基本 ( 属性 ) $ : attributes { } ); } } catch ( err) { console. warn( "打印操作失败:" + err); }
2.2. 列出打印机和详细属性
try { const printers= await printing. getPrinters(); const promises= printers. map( printer=> printer. fetchAttributes()); Promise. all( promises). then(( values) => { for ( const attributesof values) { console. log( $attributes { printerName . 具有以下 } 详细 ( 属性 ) $ : attributes { } ); } }); } catch ( err) { console. warn( "打印操作失败:" + err); }
2.3. 查询打印机状态
try { const printers= await printing. getPrinters(); const printer= printers. find( printer=> printer. cachedAttributes(). printerName=== 'Brother QL-820NWB' ); const attributes= await printer. fetchAttributes(); console. log( $attributes { printerName . 的 } 状态是 $ 新 attributes { printerState . } ! ); } catch ( err) { console. warn( "打印操作失败:" + err); }
2.4. 提交打印作业
try { const printers= await printing. getPrinters(); const printer= printers. find( printer=> printer. cachedAttributes(). printerName=== 'Brother QL-820NWB' ); const printJob= await printer. submitPrintJob( "示例打印作业" , new Blob(...), { copies: 2 , media: 'iso_a4_210x297mm' , multipleDocumentHandling: 'separate-documents-collated-copies' , printerResolution: { crossFeedDirectionResolution: 300 , feedDirectionResolution: 400 , units: 'dots-per-inch' }, sides: 'one-sided' , printQuality: 'high' , pageRanges: [{ from : 1 , to: 5 }, { from : 7 , to: 10 }], }); const printJobComplete= new Promise(( resolve, reject) => { printJob. onjobstatechange= () => { const jobState= printJob. attributes(). jobState; if ( IsErrorStatus( jobState)) { console. warn( 作业出错$ : jobState { } ); reject( /**/ ); return ; } if ( jobState=== "completed" ) { console. log( "作业完成!" ); resolve( /**/ ); return ; } console. log( 作业状态变为 $jobState { } ); }; }); await printJobComplete; } catch ( err) { console. warn( "打印操作失败:" + err); }
2.5. 取消打印作业
try { const printers= await printing. getPrinters(); const printer= printers. find( printer=> printer. cachedAttributes(). printerName=== 'Brother QL-820NWB' ); const printJob= await printer. submitPrintJob(...); // 如果作业已经完成,这可能不会生效。 printJob. cancel(); } catch ( err) { console. warn( "打印操作失败:" + err); }
3. 对 Window
接口的扩展
[Exposed =Window ,SecureContext ,IsolatedContext ]partial interface Window { [SameObject ]readonly attribute WebPrintingManager ; };printing
每个 Window
对象都关联着一个唯一的 WebPrintingManager
对象实例,该实例在 Window
对象创建时分配。
4.
WebPrintingManager
[Exposed =Window ,SecureContext ,IsolatedContext ]interface {WebPrintingManager Promise <sequence <WebPrinter >>(); };getPrinters
此接口上的方法会并行运行其某些步骤,并 通过 Web 打印任务源在主线程上重新排入任务。
getPrinters()
方法步骤为:
-
如果 this 的相关全局对象的关联 Document 不被允许使用名为 "web-printing" 的策略控制特性,则抛出一个 "
NotAllowedError"DOMException。 -
令 promise 为一个新的 promise。
-
并行运行以下步骤:
-
令 local_printers 为操作系统本地可用的所有打印机。
-
令 attributes_list 为一个由
WebPrinterAttributes字典组成的空列表。 -
对于 local_printers 中的每个 printer:
-
令 web_printer_attributes 为一个新的
WebPrinterAttributes字典。 -
将 web_printer_attributes 的
printerName设置为 printer 的名称。 -
将 web_printer_attributes 的
printerId设置为 printer 的 id。该 id 必须通过返回其自身 SHA256 哈希的十六进制字符串 表示来进行混淆。 -
将 web_printer_attributes 追加到 attributes_list。
-
-
使用排入一个全局任务在 global 上通过 Web 打印任务源运行以下 步骤:
-
令 web_printers 为一个由
WebPrinter对象组成的空列表。 -
对于 attributes_list 中的每个 attributes:
-
令 web_printer 为一个新的
WebPrinter, 其attributes 被设置为 attributes。 -
将 web_printer 追加到 web_printers。
-
-
用 web_printers 兑现 promise。
-
-
-
返回 promise。
5. WebPrinter
[Exposed =Window ,SecureContext ,IsolatedContext ]interface {WebPrinter WebPrinterAttributes ();cachedAttributes Promise <WebPrinterAttributes >();fetchAttributes Promise <WebPrintJob >(submitPrintJob USVString ,job_name Blob ,document_data optional WebPrintJobTemplateAttributes = {}); };template_attributes
每个 WebPrinter
都有 attributes,它们是
WebPrinterAttributes
的一个实例,初始时只包含
printerName
和 printerId,
若要获取更多信息,需要使用 fetchAttributes()。
cachedAttributes()
方法返回attributes。
fetchAttributes()
方法步骤为:
-
令 promise 为一个新的 promise。
-
并行运行以下步骤:
-
如果与打印机通信时出现任何问题,则用一个新的
NetworkErrorDOMException拒绝 promise,并中止这些步骤。 -
令 new_web_printer_attributes 为一个新的
WebPrinterAttributes实例。 -
查询打印机的打印机能力。令 printer_capabilities 为返回的能力列表。
-
对于 printer_capabilities 中的每个 printer_capability:
-
根据互联网打印协议 (RFC 8011),执行必要的映射,使 printer_capability 符合
WebPrinterAttributes字典。 -
使用已映射的 printer_capability 设置 new_web_printer_attributes 的对应字段 (例如,与介质来源相关的 printer_capability 应映射到
mediaSourceDefault和mediaSourceSupported的有效值)。 -
查询打印机的打印机状态。将 new_web_printer_attributes 的
printerState设置为返回的打印机状态值。 -
将 attributes 设置为 new_web_printer_attributes。
-
用 new_web_printer_attributes 兑现 promise。
-
-
返回 promise。
submitPrintJob()
方法步骤为:
-
令 promise 为一个新的 promise。
-
并行运行以下步骤:
-
如果与打印机通信时出现任何问题,则用一个新的
NetworkErrorDOMException拒绝 promise,并中止这些步骤。 -
使用
fetchAttributes()的算法来更新attributes。 -
令 printer 为执行
submitPrintJob()的那个WebPrinter实例。 -
对于
template_attributes中的每个 template_attribute: -
如果 template_attribute 不包含与对应 printer 的attributes 相比检查后受支持的值(例如,
mediaSource与mediaSourceSupported相比检查),则用一个新的DataErrorDOMException拒绝 promise,并中止这些步骤。 -
令 pdf_data 保存 PDF 文档数据。将
document_dataBlob转换为 PDF 文档,并将其设置为 pdf_data 的值。如果document_data格式错误,即不是有效的 PDF 文档,则用一个新的DataErrorDOMException拒绝 promise,并中止这些步骤。 -
将 pdf_data 与
WebPrintJobTemplateAttributes一起发送,并作为打印作业提交给打印机。 -
令 print_job 为一个
WebPrintJob接口的实例。将 print_job 与刚提交给打印机的打印作业关联起来。
-
-
用 print_job 兑现 promise。
-
返回 promise。
6. WebPrintJob
[Exposed =Window ,SecureContext ,IsolatedContext ]interface :WebPrintJob EventTarget {WebPrintJobAttributes ();attributes undefined ();cancel attribute EventHandler ; };onjobstatechange
每个 WebPrintJob
都有 attributes,它们是
WebPrintJobAttributes
的一个实例,初始时为空。
attributes()
方法返回attributes,它显示
打印作业所处的状态(例如已完成多少页)。
cancel()
方法会立即中止打印作业。
submitPrintJob()
方法时,作为 WebPrintJobTemplateAttributes
的一部分传入类型为 AbortSignal
的 signal
参数来实现取消。
onjobstatechange
是 onjobstatechange
事件类型的事件处理器 IDL 属性。
每当打印作业的 WebPrintJobState
或 jobPagesCompleted
发生变化时,用户
代理必须触发一个 onjobstatechange
事件。
7. 数据模型
WebPrinterAttributes
——表示attributes的字典。
WebPrintJobTemplateAttributes
——表示打印作业属性的字典。
dictionary {WebPrinterAttributes USVString ;printerName USVString ;printerId unsigned long ;copiesDefault WebPrintingRange ;copiesSupported WebPrintingMediaCollection ;mediaColDefault sequence <WebPrintingMediaCollection >;mediaColDatabase USVString ;mediaSourceDefault sequence <USVString >;mediaSourceSupported WebPrintingMimeMediaType ;documentFormatDefault sequence <WebPrintingMimeMediaType >;documentFormatSupported WebPrintingMultipleDocumentHandling ;multipleDocumentHandlingDefault sequence <WebPrintingMultipleDocumentHandling >;multipleDocumentHandlingSupported WebPrintingOrientationRequested ;orientationRequestedDefault sequence <WebPrintingOrientationRequested >;orientationRequestedSupported WebPrintingResolution ;printerResolutionDefault sequence <WebPrintingResolution >;printerResolutionSupported WebPrintColorMode ;printColorModeDefault sequence <WebPrintColorMode >;printColorModeSupported WebPrinterState ;printerState USVString ;printerStateMessage sequence <WebPrinterStateReason >;printerStateReasons WebPrintQuality ;printQualityDefault sequence <WebPrintQuality >;printQualitySupported WebPrintingSides ;sidesDefault sequence <WebPrintingSides >; };sidesSupported dictionary {WebPrintJobTemplateAttributes unsigned long ;copies WebPrintingMediaCollectionRequested ;mediaCol USVString ;mediaSource WebPrintingMultipleDocumentHandling ;multipleDocumentHandling WebPrintingOrientationRequested ;orientationRequested WebPrintingResolution ;printerResolution WebPrintColorMode ;printColorMode WebPrintQuality ;printQuality WebPrintingSides ;sides AbortSignal ; };signal dictionary {WebPrintingRange unsigned long ;from unsigned long ; };to dictionary {WebPrintingResolution unsigned long ;crossFeedDirectionResolution unsigned long ;feedDirectionResolution WebPrintingResolutionUnits ; };units typedef (WebPrintingRange or unsigned long );WebPrintingMediaSizeDimension dictionary {WebPrintingMediaSize WebPrintingMediaSizeDimension ;yDimension WebPrintingMediaSizeDimension ; };xDimension dictionary {WebPrintingMediaCollection USVString ;mediaSizeName WebPrintingMediaSize ; };mediaSize dictionary {WebPrintingMediaSizeRequested required unsigned long ;yDimension required unsigned long ; };xDimension dictionary {WebPrintingMediaCollectionRequested required WebPrintingMediaSizeRequested ; };mediaSize dictionary {WebPrintJobAttributes USVString ;jobName unsigned long ;jobPages unsigned long ;jobPagesCompleted WebPrintJobState ; };jobState enum {WebPrintingMimeMediaType , };"application/pdf" enum {WebPrintingMultipleDocumentHandling ,"separate-documents-collated-copies" , };"separate-documents-uncollated-copies" enum {WebPrintingOrientationRequested ,"portrait" , };"landscape" enum {WebPrintingResolutionUnits ,"dots-per-inch" , };"dots-per-centimeter" enum {WebPrintingSides ,"one-sided" ,"two-sided-long-edge" , };"two-sided-short-edge" enum {WebPrintQuality ,"draft" ,"normal" , };"high" enum {WebPrintColorMode ,"color" , };"monochrome" enum {WebPrinterState ,"idle" ,"processing" , };"stopped" enum {WebPrinterStateReason ,"none" ,"other" ,"connecting-to-device" ,"cover-open" ,"developer-empty" ,"developer-low" ,"door-open" ,"fuser-over-temp" ,"fuser-under-temp" ,"input-tray-missing" ,"interlock-open" ,"interpreter-resource-unavailable" ,"marker-supply-empty" ,"marker-supply-low" ,"marker-waste-almost-full" ,"marker-waste-full" ,"media-empty" ,"media-jam" ,"media-low" ,"media-needed" ,"moving-to-paused" ,"opc-life-over" ,"opc-near-eol" ,"output-area-almost-full" ,"output-area-full" ,"output-tray-missing" ,"paused" ,"shutdown" ,"spool-area-full" ,"stopped-partly" ,"stopping" ,"timed-out" ,"toner-empty" ,"toner-low" , };"cups-pki-expired" enum {WebPrintJobState ,"preliminary" ,"pending" ,"processing" ,"completed" ,"canceled" };"aborted"
8. 隐私和安全注意事项
8.1. 潜在问题
8.1.1. 指纹识别
WebPrinter
对象暴露了可用于指纹识别的 printerName
和 printerId
attributes。
8.1.2. 伪造打印作业
恶意代码注入可能导致:
-
向打印机提交不需要的打印作业,
-
对打印机进行拒绝服务攻击。
这种情况绝不能发生在隔离上下文中。
8.1.3. 监视
应用程序可能会观察打印机何时正在使用。
8.2. 缓解因素
8.2.1. 权限策略
本规范定义了一个策略控制特性,由
字符串 "web-printing" 标识。
其默认允许列表为 "self"。
document 的权限策略决定
该文档中的任何内容是否被允许使用 getPrinters()。
如果在任何文档中被禁用,
则该文档中的任何内容都不被允许使用
getPrinters()。
8.2.2. 用户同意
访问打印机是一个强大特性。用户代理在没有
明示许可的情况下,绝不能允许
Web 应用程序获得对 WebPrinter
对象的访问权限。
必须针对特定源获取用户同意。
同意请求必须由对
getPrinters()
方法的调用触发。用户代理必须显示
一个权限提示,清楚地指出哪个源正在请求访问权限,
并向用户提供足够的信息,以便其做出知情决定。
用户代理应该同时提供临时 (例如,“仅限本次会话”)和持久权限选项。 为了降低用户忘记自己已授予持久 访问权限的风险,临时权限应该是默认且更醒目的选项。
必须向用户提供一种机制,用于查看和撤销此前 授予此 API 的任何权限。