Web 打印 API

非官方提案草案,

此版本:
https://github.com/bylica-at-google/web-printing-api
问题跟踪:
GitHub
编辑:
Dominik Bylica (Google)

摘要

本文档定义了一个新的 Web 打印 API,允许应用开发者通过在隔离上下文中直接访问打印机,来构建出色的 打印机相关功能。

本文档状态

本规范由 Web 平台孵化器 社区组发布。 它不是 W3C 标准,也不在 W3C 标准轨道上。 请注意,根据 W3C 社区贡献者许可协议 (CLA) 存在有限的退出权,且适用其他条件。 了解更多关于 W3C 社区组和业务组的信息。

1. 引言

Web 打印 API 为在隔离上下文中实现打印 功能带来了前所未有的灵活性。该 API 不会暴露在普通 Web 上下文中。

1.1. 现有替代方案

目前存在一个 window.print() 方法,但它基本上只是 打开一个打印对话框,并要求用户完成剩余操作。Web 打印 API 使应用开发者可以从隔离上下文中的 Web 应用直接访问操作系统本地可用的打印机, 并允许使用自定义 打印属性(例如纸张大小、颜色设置、质量等)提交打印作业。

1.2. Web 打印 API 功能

使用 Web 打印 API,你可以:

Web 打印 API 以互联网打印协议为模型。 这意味着打印机能力和打印属性, 以及它们的名称、可能值、有效值,都 来自定义互联网打印协议的标准文档中的定义 (RFC8011)。

RFC8011 第 5 节对于进一步了解尤其有帮助 (https://datatracker.ietf.org/doc/html/rfc8011#section-5)。

2. 示例

2.1. 列出打印机和基本属性

try {
  const printers = await printing.getPrinters();
  for (const printer of 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 attributes of 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() 方法步骤为:
  1. 如果 this相关全局对象关联 Document被允许使用名为 "web-printing" 的策略控制特性,则抛出一个 "NotAllowedError" DOMException

  2. promise一个新的 promise

  3. globalthis相关全局对象

  4. 并行运行以下步骤:

    1. local_printers 为操作系统本地可用的所有打印机。

    2. attributes_list 为一个由 WebPrinterAttributes 字典组成的空列表

    3. 对于 local_printers 中的每个 printer

      1. web_printer_attributes 为一个新的 WebPrinterAttributes 字典。

      2. web_printer_attributesprinterName 设置为 printer 的名称。

      3. web_printer_attributesprinterId 设置为 printer 的 id。该 id 必须通过返回其自身 SHA256 哈希的十六进制字符串 表示来进行混淆。

      4. web_printer_attributes 追加到 attributes_list

    4. 使用排入一个全局任务global 上通过 Web 打印任务源运行以下 步骤:

      1. web_printers 为一个由 WebPrinter 对象组成的空列表

      2. 对于 attributes_list 中的每个 attributes

        1. web_printer 为一个新的 WebPrinter, 其attributes 被设置为 attributes

        2. web_printer 追加到 web_printers

      3. web_printers 兑现 promise

  5. 返回 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 的一个实例,初始时只包含 printerNameprinterId, 若要获取更多信息,需要使用 fetchAttributes()

cachedAttributes() 方法返回attributes

fetchAttributes() 方法步骤为:
  1. promise一个新的 promise

  2. 并行运行以下步骤:

    1. 如果与打印机通信时出现任何问题,则用一个新的 NetworkError DOMException 拒绝 promise,并中止这些步骤。

    2. new_web_printer_attributes 为一个新的 WebPrinterAttributes 实例。

    3. 查询打印机的打印机能力。令 printer_capabilities 为返回的能力列表。

    4. 对于 printer_capabilities 中的每个 printer_capability

    5. 根据互联网打印协议 (RFC 8011),执行必要的映射,使 printer_capability 符合 WebPrinterAttributes 字典。

    6. 使用已映射的 printer_capability 设置 new_web_printer_attributes 的对应字段 (例如,与介质来源相关的 printer_capability 应映射到 mediaSourceDefaultmediaSourceSupported 的有效值)。

    7. 查询打印机的打印机状态。将 new_web_printer_attributesprinterState 设置为返回的打印机状态值。

    8. attributes 设置为 new_web_printer_attributes

    9. new_web_printer_attributes 兑现 promise

  3. 返回 promise

submitPrintJob() 方法步骤为:
  1. promise一个新的 promise

  2. 并行运行以下步骤:

    1. 如果与打印机通信时出现任何问题,则用一个新的 NetworkError DOMException 拒绝 promise,并中止这些步骤。

    2. 使用 fetchAttributes() 的算法来更新attributes

    3. printer 为执行 submitPrintJob() 的那个 WebPrinter 实例。

    4. 对于 template_attributes 中的每个 template_attribute

    5. 如果 template_attribute 不包含与对应 printerattributes 相比检查后受支持的值(例如,mediaSourcemediaSourceSupported 相比检查),则用一个新的 DataError DOMException 拒绝 promise,并中止这些步骤。

    6. pdf_data 保存 PDF 文档数据。将 document_data Blob 转换为 PDF 文档,并将其设置为 pdf_data 的值。如果 document_data 格式错误,即不是有效的 PDF 文档,则用一个新的 DataError DOMException 拒绝 promise,并中止这些步骤。

    7. pdf_dataWebPrintJobTemplateAttributes 一起发送,并作为打印作业提交给打印机。

    8. print_job 为一个 WebPrintJob 接口的实例。将 print_job 与刚提交给打印机的打印作业关联起来。

  3. print_job 兑现 promise

  4. 返回 promise

类型为 AbortSignalsignal 也可用于 cancel() 打印作业。

6. WebPrintJob

[Exposed=Window, SecureContext, IsolatedContext]
interface WebPrintJob : EventTarget {
  WebPrintJobAttributes attributes();
  undefined cancel();

  attribute EventHandler onjobstatechange;
};

每个 WebPrintJob 都有 attributes,它们是 WebPrintJobAttributes 的一个实例,初始时为空。

attributes() 方法返回attributes,它显示 打印作业所处的状态(例如已完成多少页)。

cancel() 方法会立即中止打印作业。

也可以通过在调用 submitPrintJob() 方法时,作为 WebPrintJobTemplateAttributes 的一部分传入类型为 AbortSignalsignal 参数来实现取消。

onjobstatechangeonjobstatechange 事件类型的事件处理器 IDL 属性。 每当打印作业的 WebPrintJobStatejobPagesCompleted 发生变化时,用户 代理必须触发一个 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 对象暴露了可用于指纹识别的 printerNameprinterId attributes

8.1.2. 伪造打印作业

恶意代码注入可能导致:

这种情况绝不能发生在隔离上下文中。

8.1.3. 监视

应用程序可能会观察打印机何时正在使用。

8.2. 缓解因素

IDL 要求此 API 只暴露在隔离上下文中。

8.2.1. 权限策略

本规范定义了一个策略控制特性,由 字符串 "web-printing" 标识。 其默认允许列表"self"

document权限策略决定 该文档中的任何内容是否被允许使用 getPrinters()。 如果在任何文档中被禁用, 则该文档中的任何内容都不被允许使用 getPrinters()

访问打印机是一个强大特性。用户代理在没有 明示许可的情况下,绝不能允许 Web 应用程序获得对 WebPrinter 对象的访问权限。

必须针对特定获取用户同意。 同意请求必须由对 getPrinters() 方法的调用触发。用户代理必须显示 一个权限提示,清楚地指出哪个源正在请求访问权限, 并向用户提供足够的信息,以便其做出知情决定。

用户代理应该同时提供临时 (例如,“仅限本次会话”)和持久权限选项。 为了降低用户忘记自己已授予持久 访问权限的风险,临时权限应该是默认且更醒目的选项。

必须向用户提供一种机制,用于查看和撤销此前 授予此 API 的任何权限。

一致性

文档 约定

一致性要求通过描述性断言 和 RFC 2119 术语的组合来表达。 本文档规范性部分中的关键词 “MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、 “MAY” 和 “OPTIONAL” 应按 RFC 2119 中的描述进行解释。 但是,为了可读性, 这些词在本规范中并不全部以大写字母出现。

除明确标记为非规范性的章节、示例和注释外, 本规范的所有文本都是规范性的。[RFC2119]

本规范中的示例以 “for example” 等词引入, 或使用 class="example" 与规范性文本分隔开来, 如下所示:

这是一个资料性示例的例子。

资料性注释以 “Note” 一词开头, 并使用 class="note" 与规范性文本分隔开来, 如下所示:

注意,这是一个资料性注释。

索引

本规范定义的术语

由引用 定义的术语

参考文献

规范性参考文献

[DOM]
Anne van Kesteren. DOM 标准。现行标准。 URL: https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript 语言规范。URL: https://tc39.es/ecma262/multipage/
[FileAPI]
Marijn Kruisselbrink. File API。URL: https://w3c.github.io/FileAPI/
[HTML]
Anne van Kesteren; et al. HTML 标准。 现行标准。URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准。现行标准。URL: https://infra.spec.whatwg.org/
[ISOLATED-CONTEXTS]
Isolated Contexts。社区组报告草案。URL: https://wicg.github.io/isolated-web-apps/isolated-contexts.html
[PERMISSIONS]
Marcos Caceres; Mike Taylor. Permissions。URL: https://w3c.github.io/permissions/
[PERMISSIONS-POLICY-1]
Ian Clelland. Permissions Policy。URL: https://w3c.github.io/webappsec-permissions-policy/
[RFC2119]
S. Bradner. RFC 中用于表示要求级别的关键词。1997 年 3 月。当前最佳实践。URL: https://datatracker.ietf.org/doc/html/rfc2119
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 标准。现行标准。 URL: https://webidl.spec.whatwg.org/

IDL 索引

[Exposed=Window, SecureContext, IsolatedContext]
partial interface Window {
  [SameObject] readonly attribute WebPrintingManager printing;
};

[Exposed=Window, SecureContext, IsolatedContext]
interface WebPrintingManager {
  Promise<sequence<WebPrinter>> getPrinters();
};

[Exposed=Window, SecureContext, IsolatedContext]
interface WebPrinter {
  WebPrinterAttributes cachedAttributes();
  Promise<WebPrinterAttributes> fetchAttributes();
  Promise<WebPrintJob> submitPrintJob(
    USVString job_name,
    Blob document_data,
    optional WebPrintJobTemplateAttributes template_attributes = {});
};

[Exposed=Window, SecureContext, IsolatedContext]
interface WebPrintJob : EventTarget {
  WebPrintJobAttributes attributes();
  undefined cancel();

  attribute EventHandler onjobstatechange;
};

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"
};