PDF.js Deep Dive from Demo 2: getDocument
October 27, 2026 · StreamPDF Team

Introduction
In the previous article, we passed a single argument: an object parameter with the property
- src: the URL of the PDF file, set to
https://assets.streampdf.com/PDF32000_2008.pdf
The getDocument call
Based on the definition of getDocument in display/api.js, the execution flow is as follows.
Lines 1-2: Initialize the loading task
Instantiate the PDFDocumentLoadingTask class: click to view the call, and assign the return value to the task variable.
Extract docId from task; at this point docId is d1.
Lines 3-15: Validate and process parameters
This block of code expands the user-provided src object into 35+ local variables: provided values are validated and type-converted, while missing ones fall back to defaults. The demo only passes url, so the vast majority of parameters take the default branch.
The only provided url is converted into a URL object by getUrlProp:
const url = URL.parse('https://assets.streampdf.com/PDF32000_2008.pdf', window.location);
// const url = src.url ? getUrlProp(src.url) : null;All other parameters are absent. In source order, the computed results are:
| Variable | Result | Logic |
|---|---|---|
data | null | If provided, normalized to Uint8Array by getDataProp |
httpHeaders | null | Passed through as-is; null when absent |
withCredentials | false | Only accepts === true |
password | null | ?? null |
rangeTransport | null | Only accepts a PDFDataRangeTransport instance |
rangeChunkSize | 2 ** 16 (64KB) | Only accepts the provided value if it is a positive integer |
worker | null | Only accepts a PDFWorker instance |
verbosity | undefined | Taken verbatim from src.verbosity |
docBaseUrl | null | Only accepts a string that is not a data: URL |
cMapUrl | null | getFactoryUrlProp: non-string becomes null, and it must end with / |
cMapPacked | true | !== false |
iccUrl | null | Same as cMapUrl |
standardFontDataUrl | null | Same as cMapUrl |
wasmUrl | null | Same as cMapUrl |
ignoreErrors | true | src.stopAtErrors !== true, see below |
maxImageSize | -1 (unlimited) | Only accepted if an integer and > -1 |
isOffscreenCanvasSupported | true | Defaults to !isNodeJS when absent |
isImageDecoderSupported | true | Defaults to !isNodeJS when absent |
canvasMaxAreaInBytes | -1 (unlimited) | Only accepted if an integer |
disableFontFace | false | Defaults to isNodeJS when absent |
fontExtraProperties | false | Only accepts === true |
enableXfa | false | Only accepts === true |
ownerDocument | globalThis.document | Defaults to the global document when absent |
disableRange | false | Only accepts === true |
disableStream | false | Only accepts === true |
disableAutoFetch | false | Only accepts === true |
pdfBug | false | Only accepts === true |
CanvasFactory | DOMCanvasFactory | Environment branch, see below |
FilterFactory | DOMFilterFactory | Same as above |
BinaryDataFactory | DOMBinaryDataFactory | Same as above |
enableHWA | false | Only accepts === true |
enableWebGPU | false | Only accepts === true |
gpuPromise | Promise.resolve(false) | WebGPU is off, so GPU initialization does not start |
useWasm | true | !== false |
pagesMapper | new PagesMapper() | Created fresh when absent |
useSystemFonts | true | Cascading default, see below |
useWorkerFetch | false | Compound check, see the Q&A below |
styleElement | null | Only preserved in TESTING builds; always null in production |
These are all the local variables produced by this block of code. Most of the checks are trivial "fall back to default when absent" cases, but a few deserve a closer look.
ignoreErrors: a semantically inverted internal variable
The user-facing parameter is called stopAtErrors, yet the internal variable is called ignoreErrors, computed as src.stopAtErrors !== true. In other words, the default behavior is to ignore errors: when parsing fails, try to recover rather than rejecting promises such as getOperatorList / getTextContent / RenderTask. When reading the source, it is very easy to copy the name and value the wrong way around here.
rangeChunkSize: the 64KB setup for later
rangeChunkSize defaults to 64KB. Since disableRange = false, when the transport is created later, range requests will be issued against the PDF, and each fetched chunk will be exactly this size. Keep it in mind for now.
docBaseUrl: why data: URLs are excluded
The source comment explains the reason: a data: URL cannot be used to resolve absolute paths, and it contains the entire PDF document with no upper bound on length, so it is not sent to the worker thread.
gpuPromise: designed to run in parallel with worker startup
const gpuPromise = Promise.resolve(false); // enableWebGPU ? initGPU() : Promise.resolve(false);Even when WebGPU is disabled, this immediately returns a resolved promise instead of deferring the check until it is used. The comment explains the intent: let GPU initialization run in parallel with worker startup, with the resolved boolean forwarded to the worker afterwards.
The three Factories: environment branches
const CanvasFactory = DOMCanvasFactory;
// src.CanvasFactory || (GENERIC build && isNodeJS ? NodeCanvasFactory : DOMCanvasFactory);The same applies to FilterFactory and BinaryDataFactory. The check has three tiers: user-provided custom > Node environment implementation > DOM implementation. The demo runs in a browser without customization, so all three factories are the DOM versions. Their responsibilities:
CanvasFactory: creates canvases for renderingFilterFactory: creates image filters (such as SVG filters)BinaryDataFactory: downloads built-in static assets; works together withuseWorkerFetchbelow
useSystemFonts: the default depends on other parameters
const useSystemFonts = true;
// typeof src.useSystemFonts === "boolean" ? src.useSystemFonts : !isNodeJS && !disableFontFace;Its default depends on disableFontFace, which was just parsed above. Why? Fonts not embedded in the PDF are loaded as FontFaces; if disableFontFace is true, that loading path is disabled, and falling back to system fonts at that point would produce completely garbled glyphs. So in that scenario the default is simply false as well, avoiding rendering utterly broken fonts.
useWorkerFetch: a compound check
const useWorkerFetch = false;
// typeof src.useWorkerFetch === "boolean"
// ? src.useWorkerFetch
// : MOZCENTRAL ||
// !!(
// BinaryDataFactory === DOMBinaryDataFactory &&
// cMapUrl && cMapPacked && standardFontDataUrl && wasmUrl &&
// isValidFetchUrl(cMapUrl, document.baseURI) &&
// isValidFetchUrl(standardFontDataUrl, document.baseURI) &&
// isValidFetchUrl(wasmUrl, document.baseURI)
// );Q: What is this check doing? A: It decides who downloads the built-in static assets (CMap files, standard font files, wasm files): the worker thread downloads them itself via the Fetch API (
true), or the main thread downloads them and forwards them to the worker (false).If the user explicitly passes a boolean, the user's value is used directly. Otherwise the default is computed: MOZCENTRAL (the Firefox built-in viewer) is always
true; for a regular build, all three of the following conditions must hold at the same time for it to betrue:
- No custom
BinaryDataFactory— because whenuseWorkerFetchistrue, that parameter is simply ignored (insidetransportFactory,binaryDataFactoryis set tonull). A user-provided factory must be respected, so the default cannot betrue;cMapUrl/standardFontDataUrl/wasmUrlare all provided, andcMapPackedistrue— without URLs there is nothing to fetch;- These three URLs resolve to http/https relative to
document.baseURI— the Fetch API only supports http/https, not file/ftp.The demo in this article only passes
url, socMapUrlisnull, condition 2 is not met, and thereforeuseWorkerFetch = false: the main-thread path is taken, withDOMBinaryDataFactorydownloading on the main thread, and the worker requesting data from the main thread viaFetchBinaryDatamessages when needed.
Line 16: Set the main-thread log level
setVerbosityLevel(verbosity); // verbosity is undefinedverbosity is undefined, so the main-thread log level stays at its default. Note that this only affects the main thread; the worker thread's log level is passed separately when the worker is created.
Lines 17-25: Instantiate the three factories
const transportFactory = {
canvasFactory: new DOMCanvasFactory({ ownerDocument: document, enableHWA: false }),
filterFactory: new DOMFilterFactory({ docId: 'd1', ownerDocument: document }),
binaryDataFactory: new DOMBinaryDataFactory({
cMapUrl: null,
standardFontDataUrl: null,
wasmUrl: null,
}),
};
// binaryDataFactory: null when MOZCENTRAL or useWorkerFetch is trueThe three Factory classes selected earlier are instantiated and packaged as transportFactory, which is then handed to the transport. Since useWorkerFetch = false, binaryDataFactory is not null — echoing the Q&A in the previous section: built-in static assets are downloaded on the main thread.
The internal capabilities of the three factory instances will be covered in a separate article.
Lines 26-33: Create the worker
worker = PDFWorker.create({ verbosity: undefined, port: undefined });
// port comes from GlobalWorkerOptions.workerPort, which the demo does not set
task._worker = worker;The demo does not pass a worker, so PDF.js creates and owns one itself. PDFWorker.create synchronously returns a PDFWorker instance; its constructor reads GlobalWorkerOptions.workerSrc (the worker file URL configured in the first article), creates a Worker, and completes the startup handshake through ready / test messages — covered in detail in Article 8. For now, just remember: the returned worker object has a promise on it, which resolves once the worker is ready.
Lines 34-63: Assemble the two parameter packages
The first is docParams, which will later be sent across threads to the worker:
const docParams = {
docId: 'd1',
apiVersion: '6.3.289', // build version number
data: null,
password: null,
disableAutoFetch: false,
rangeChunkSize: 65536,
docBaseUrl: null,
enableXfa: false,
evaluatorOptions: {
maxImageSize: -1,
disableFontFace: false,
ignoreErrors: true,
isOffscreenCanvasSupported: true,
isImageDecoderSupported: true,
canvasMaxAreaInBytes: -1,
fontExtraProperties: false,
useSystemFonts: true,
useWasm: true,
useWorkerFetch: false,
cMapUrl: null,
cMapPacked: true,
iccUrl: null,
standardFontDataUrl: null,
wasmUrl: null,
hasGPU: false, // placeholder, backfilled later
},
};The second is transportParams, which stays on the main thread:
const transportParams = {
ownerDocument: document,
pdfBug: false,
styleElement: null,
enableHWA: false,
loadingParams: { disableAutoFetch: false, enableXfa: false },
};One sentence to distinguish them: docParams is the "work order" sent to the worker, while transportParams is the "local configuration" kept on the main thread.
From line 64: the handshake after the worker is ready
Promise.all([worker.promise, gpuPromise]).then(...)Once both worker readiness and GPU probing (resolving to false) complete, the following happens in sequence:
hasGPU = falseis backfilled intoevaluatorOptions;- A
GetDocRequestmessage is sent to the worker withdocParamsas the payload. Sincedataisnull, there is no buffer whose ownership needs to be transferred; - Choose the network stream: with
databeingnullandrangeTransportbeingnull, theurlbranch is taken and a network stream is instantiated (the fetch implementation is preferred, with a fallback to XHR when unsupported; see Article 9 for how range requests are issued internally):
networkStream = new NetworkStream({
url,
httpHeaders: null,
withCredentials: false,
rangeChunkSize: 65536,
disableRange: false,
disableStream: false,
});- After receiving the worker's acknowledgment of
GetDocRequest(workerId), aMessageHandlerand aWorkerTransportare created and attached totask._transport— covered in Articles 6 and 7 respectively; - A
Readymessage is sent to the worker, declaring that the main-thread side is ready; the worker'sReadyhandler then callssetupDoc(docParams).
Return value: task
All of the above .then chain proceeds asynchronously in the background, while getDocument itself returns task synchronously. If any step in the chain fails, the capability inside task is rejected — the very promise that the demo waits on with await loadingTask.promise.
At this point, getDocument has finished executing.
Summary
In this section we followed the demo through the complete getDocument call:
- Create
PDFDocumentLoadingTaskand extractdocId = 'd1'; - Expand
srcinto 35+ local variables; the demo only passesurl, and everything else falls back to defaults; - Instantiate the three DOM factories and package them as
transportFactory; PDFWorker.createcreates the worker and starts the handshake;- Assemble
docParams(sent to the worker) andtransportParams(kept on the main thread); - Once the worker is ready, send
GetDocRequest, set up the network stream, createMessageHandlerandWorkerTransport, and finally sendReady; - Return
tasksynchronously; the demo waits for the document to be ready viaawait loadingTask.promise.
Along the way we deliberately skipped four "heavyweight players": the creation and handshake of PDFWorker, the messaging mechanism of MessageHandler, the initialization of WorkerTransport, and the range request strategy of the network stream. Each of them gets its own article, starting with the next one.
Related articles
- PDF.js Deep Dive from Demo 10: CanvasGraphics and operatorList RenderingOctober 27, 2026
- PDF.js Deep Dive from Demo 9: Network Streams and Range RequestsOctober 27, 2026
- PDF.js Deep Dive from Demo 8: PDFWorker.create and the Real/Fake Worker HandshakeOctober 27, 2026
- PDF.js Deep Dive from Demo 7: WorkerTransport InitializationOctober 27, 2026
- PDF.js Deep Dive from Demo 6: MessageHandler Cross-Thread CommunicationOctober 27, 2026
- PDF.js Deep Dive from Demo 5: renderOctober 27, 2026
- PDF.js Deep Dive from Demo 4: getViewportOctober 27, 2026
- PDF.js Deep Dive from Demo 3: getPageOctober 27, 2026
- PDF.js Source Walkthrough: TextLayerOctober 27, 2026
- PDF.js Source Walkthrough: font_loaderOctober 27, 2026
- PDF.js Source Walkthrough: obj_bin_transform_displayOctober 27, 2026
- PDF.js Source Walkthrough: PDFObjectsOctober 27, 2026
- PDF.js Source Walkthrough: Network StreamsOctober 27, 2026
- PDF.js Source Walkthrough: internal_evt.jsOctober 27, 2026
- PDF.js Source Walkthrough: math_clamp.jsOctober 27, 2026
- PDF.js Source Walkthrough: css_utils.jsOctober 27, 2026
- PDF.js Source Walkthrough: scripting_utilsOctober 27, 2026
- PDF.js Source Walkthrough: MurmurHash3_64October 27, 2026
- PDF.js Source Walkthrough: obj_bin_transform_utilsOctober 27, 2026
- PDF.js Source Walkthrough: image_utils.jsOctober 27, 2026
- PDF.js Source Walkthrough: BasePDFStreamOctober 27, 2026
- PDF.js Source Walkthrough: Shared UtilitiesOctober 27, 2026
- PDF.js Source Walkthrough: MessageHandlerOctober 27, 2026
- PDF.js Source Walkthrough: apiOctober 27, 2026
- PDF.js Source Walkthrough: fontsOctober 27, 2026
- PDF.js Source Walkthrough: glyphlistOctober 27, 2026
- PDF.js Source Walkthrough: binary_cmapOctober 27, 2026
- PDF.js Source Walkthrough: cmapOctober 27, 2026
- PDF.js Source Walkthrough: encodingsOctober 27, 2026
- PDF.js Source Walkthrough: metricsOctober 27, 2026
- PDF.js Source Walkthrough: standard_fontsOctober 27, 2026
- PDF.js Source Walkthrough: fonts_utilsOctober 27, 2026
- PDF.js Source Walkthrough: default_appearanceOctober 27, 2026
- PDF.js Source Walkthrough: metadata_parserOctober 27, 2026
- PDF.js Source Walkthrough: name_number_treeOctober 27, 2026
- PDF.js Source Walkthrough: struct_treeOctober 27, 2026
- PDF.js Source Walkthrough: catalogOctober 27, 2026
- PDF.js Source Walkthrough: pdf_managerOctober 27, 2026
- PDF.js Source Walkthrough: object_loaderOctober 27, 2026
- PDF.js Source Walkthrough: obj_bin_transform_coreOctober 27, 2026
- PDF.js Source Walkthrough: cryptoOctober 27, 2026
- PDF.js Source Walkthrough: core_utilsOctober 27, 2026
- PDF.js Source Walkthrough: primitivesOctober 27, 2026
- PDF.js Source Walkthrough: worker_streamOctober 27, 2026
- PDF.js Source Walkthrough: ccitt_streamOctober 27, 2026
- PDF.js Source Walkthrough: jpx_streamOctober 27, 2026
- PDF.js Source Walkthrough: jbig2_streamOctober 27, 2026
- PDF.js Source Walkthrough: jpeg_streamOctober 27, 2026
- PDF.js Source Walkthrough: decrypt_streamOctober 27, 2026
- PDF.js Source Walkthrough: brotli_streamOctober 27, 2026
- PDF.js Source Walkthrough: run_length_streamOctober 27, 2026
- PDF.js Source Walkthrough: ascii_hex_streamOctober 27, 2026
- PDF.js Source Walkthrough: ascii_85_streamOctober 27, 2026
- PDF.js Source Walkthrough: lzw_streamOctober 27, 2026
- PDF.js Source Walkthrough: predictor_streamOctober 27, 2026
- PDF.js Source Walkthrough: flate_streamOctober 27, 2026
- PDF.js Source Walkthrough: decode_streamOctober 27, 2026
- PDF.js Source Walkthrough: chunked_streamOctober 27, 2026
- PDF.js Source Walkthrough: streamOctober 27, 2026
- PDF.js Source Walkthrough: base_streamOctober 27, 2026
- PDF.js Deep Dive 11: ParserOctober 26, 2026
- PDF.js Deep Dive 10: XRefOctober 26, 2026
- PDF.js Deep Dive 9: CanvasGraphicsOctober 26, 2026
- PDF.js Deep Dive 8: PDFDocumentOctober 26, 2026
- The AI Era: What Should We Do to Survive? (Reflections on Founding StreamPDF)October 26, 2026
- PDF.js Deep Dive 7: PartialEvaluatorOctober 25, 2026
- PDF.js Deep Dive 6: PDFManager.initDocumentOctober 25, 2026
- PDF.js Deep Dive 5: PDFFetchStream and PDFWorkerStreamOctober 25, 2026
- PDF.js Deep Dive 4: MessageHandlerOctober 22, 2026
- PDF.js Deep Dive 3: getViewport & renderOctober 22, 2026
- PDF.js Deep Dive from Demo 1: Rendering a PDF with a Custom ViewerOctober 19, 2026
- PDF.js Deep Dive 2: getDocument & getPageOctober 19, 2026
- PDF.js Deep Dive 1: The Architecture of PDF.jsOctober 16, 2026