Web Printing API

Unofficial Proposal Draft,

This version:
https://github.com/bylica-at-google/web-printing-api
Issue Tracking:
GitHub
Editor:
Dominik Bylica (Google)

Abstract

This document defines a new Web Printing API that allows the app developers to build wonderful printer-related features by giving them direct access to printers in isolated contexts.

Status of this document

This specification was published by the Web Platform Incubator Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

1. Introduction

The Web Printing API brings unprecedented flexibility to implementing printing features in isolated contexts. The API is not exposed in ordinary web contexts.

1.1. Existing alternative

There is a window.print() method that exists, but it basically opens a print dialog and asks the user to do the rest. The Web Printing API gives app developers access to printers locally available to the operating system directly from the web applications in isolated contexts, and allows submitting print jobs with custom print attributes (like paper size, color settings, quality, etc.).

1.2. The Web Printing API features

Using the Web Printing API you can:

The Web Printing API is modeled after the Internet Printing Protocol. That means the printer capabilities and print attributes, with their names, possible values, valid values, all come from their definitions in the standards document defining the Internet Printing Protocol (RFC8011).

Section 5 of the RFC8011 is especially helpful to learn more (https://datatracker.ietf.org/doc/html/rfc8011#section-5).

2. Examples

2.1. Listing Printers & Basic Attributes

try {
  const printers = await printing.getPrinters();
  for (const printer of printers) {
    const attributes = printer.cachedAttributes();
    console.log(
      ${attributes.printerName} has the following (basic) attributes: ${attributes});
  }
} catch (err) {
  console.warn("Printing operation failed: " + err);
}

2.2. Listing Printers & Detailed Attributes

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} has the following (detailed) attributes: ${attributes});
      }
    });
} catch (err) {
  console.warn("Printing operation failed: " + err);
}

2.3. Querying Printer State

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}'s new state is ${attributes.printerState}!);
} catch (err) {
  console.warn("Printing operation failed: " + err);
}

2.4. Submitting a Print Job

try {
  const printers = await printing.getPrinters();
  const printer = printers.find(
    printer => printer.cachedAttributes().printerName === 'Brother QL-820NWB');

  const printJob = await printer.submitPrintJob("Sample Print Job",
    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(Job errored: ${jobState});
        reject(/**/);
        return;
      }
      if (jobState === "completed") {
        console.log("Job complete!");
        resolve(/**/);
        return;
      }
      console.log(Job state changed to ${jobState});
    };
  });
  await printJobComplete;
} catch (err) {
  console.warn("Printing operation failed: " + err);
}

2.5. Canceling a Print Job

try {
  const printers = await printing.getPrinters();
  const printer = printers.find(
    printer => printer.cachedAttributes().printerName === 'Brother QL-820NWB');

  const printJob = await printer.submitPrintJob(...);

  // This might take no effect if the job has already finished.
  printJob.cancel();

} catch (err) {
  console.warn("Printing operation failed: " + err);
}

3. Extensions to the Window interface

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

Each Window object is associated with a unique instance of a WebPrintingManager object, allocated when the Window object is created.

4. WebPrintingManager

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

The methods on this interface run some of their steps in parallel, and queue tasks back on the main thread via the web printing task source.

The getPrinters() method steps are:
  1. If this’s relevant global object’s associated Document is not allowed to use the policy-controlled feature named "web-printing", throw a "NotAllowedError" DOMException.

  2. Let promise be a new promise.

  3. Let global be this’s relevant global object.

  4. Run the following steps in parallel:

    1. Let local_printers be all printers locally available to the operating system.

    2. Let attributes_list be an empty list of WebPrinterAttributes dictionaries.

    3. For each printer of local_printers:

      1. Let web_printer_attributes be a new WebPrinterAttributes dictionary.

      2. Set web_printer_attributes’s printerName to printer’s name.

      3. Set web_printer_attributes’s printerId to printer’s id. The id MUST be obfuscated by returning a hex string representation of a SHA256 hash of itself.

      4. Append web_printer_attributes to attributes_list.

    4. Queue a global task on global using the web printing task source to run these steps:

      1. Let web_printers be an empty list of WebPrinter objects.

      2. For each attributes of attributes_list:

        1. Let web_printer be a new WebPrinter whose attributes is set to attributes.

        2. Append web_printer to web_printers.

      3. Resolve promise with web_printers.

  5. Return 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 = {});
};

Each WebPrinter has attributes, which are an instance of WebPrinterAttributes, initially containing only the printerName and printerId, to obtain more, fetchAttributes() needs to be used.

cachedAttributes() method returns the attributes.

The fetchAttributes() method steps are:
  1. Let promise be a new promise.

  2. Run the following steps in parallel:

    1. If there is any problem while communicating with the printer, reject promise with a new NetworkError DOMException and abort these steps.

    2. Let new_web_printer_attributes be a new instance of WebPrinterAttributes.

    3. Query the printer for printer capabilities. Let printer_capabilities be a list of returned capabilities.

    4. For each printer_capability of printer_capabilities:

    5. Perform the necessary mapping to conform the printer_capability with the WebPrinterAttributes dictionary according to the Internet Printing Protocol (RFC 8011).

    6. Set the corresponding fields of new_web_printer_attributes with the mapped printer_capability (e.g. printer_capability related to media source should be mapped to valid values of mediaSourceDefault and mediaSourceSupported).

    7. Query the printer for printer state. Set the printerState of new_web_printer_attributes to the returned printer state value.

    8. Set the attributes to new_web_printer_attributes.

    9. Resolve promise with new_web_printer_attributes.

  3. Return promise.

The submitPrintJob() method steps are:
  1. Let promise be a new promise.

  2. Run the following steps in parallel:

    1. If there is any problem while communicating with the printer, reject promise with a new NetworkError DOMException and abort these steps.

    2. Use the algorithm from fetchAttributes() to update attributes.

    3. Let printer be an instance of WebPrinter the submitPrintJob() is executed on.

    4. For each template_attribute of template_attributes:

    5. If template_attribute does not contain supported values checked against a corresponding printer’s attributes (e.g. mediaSource checked against mediaSourceSupported), reject promise with a new DataError DOMException and abort these steps.

    6. Let pdf_data hold a PDF document data. Transform document_data Blob to a PDF document and set as value of pdf_data. If document_data is malformed, i.e. not a valid PDF document, reject promise with a new DataError DOMException and abort these steps.

    7. Send pdf_data alongside with WebPrintJobTemplateAttributes and submit as a print job to the printer.

    8. Let print_job be an instance of a WebPrintJob interface. Associate print_job with the print job just submitted to the printer.

  3. Resolve promise with print_job.

  4. Return promise.

signal of type AbortSignal can be used to cancel() print job as well.

6. WebPrintJob

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

  attribute EventHandler onjobstatechange;
};

Each WebPrintJob has attributes, which are an instance of WebPrintJobAttributes, initially empty.

attributes() method returns attributes which shows the state the print job is in (e.g. how many pages completed).

cancel() method aborts the print job immediately.

Cancelling can be achieved using a signal param of type AbortSignal passed as a part of the WebPrintJobTemplateAttributes when calling the submitPrintJob() method.

onjobstatechange is the event handler IDL attribute for the onjobstatechange event type. The user agent MUST fire a onjobstatechange event whenever the WebPrintJobState or jobPagesCompleted of the print job changes.

7. Data model

WebPrinterAttributes - dictionary representing the attributes.

WebPrintJobTemplateAttributes - dictionary representing the print job attributes.

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. Privacy & Security Considerations

8.1. Potential issues

8.1.1. Fingerprinting

The WebPrinter object exposes printerName and printerId attributes that can be used for fingerprinting.

8.1.2. Forging print jobs

Malicious code injection could lead to:

This MUST never happen in isolated context.

8.1.3. Surveillance

Applications could potentially observe when printers are in use.

8.2. Mitigating factors

The IDL mandates that this API is only exposed in isolated context.

8.2.1. Permissions Policy

This specification defines a policy-controlled feature identified by the string "web-printing". Its default allowlist is "self".

A document’s permissions policy determines whether any content in that document is allowed to use getPrinters(). If disabled in any document, no content in the document will be allowed to use getPrinters().

Access to printers is a powerful feature. A user agent MUST NOT allow a web application to gain access to a WebPrinter object without express permission.

User consent MUST be obtained for a specific origin. The request for consent MUST be triggered by a call to the getPrinters() method. The user agent MUST display a permission promptthat clearly indicates which origin is requesting access and provides the user with enough information to make an informed decision.

User agents SHOULD provide options for both transient (e.g., "for this session only") and persistent permission. To mitigate the risk of users forgetting they have granted persistent access, transient permission SHOULD be the default and more prominent option.

Users MUST be provided with a mechanism to view and revoke any previously granted permissions for this API.

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript Language Specification. URL: https://tc39.es/ecma262/multipage/
[FileAPI]
Marijn Kruisselbrink. File API. URL: https://w3c.github.io/FileAPI/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[ISOLATED-CONTEXTS]
Isolated Contexts. Draft Community Group Report. 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. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

IDL Index

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