tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 2cb60fac4cd05d426e8dc8caa9bbce182ff91195
parent ee22a4cf3c80c1e80f92b1ed210d58e6cfe83aca
Author: Updatebot <updatebot@mozilla.com>
Date:   Mon, 10 Nov 2025 15:10:04 +0000

Bug 1999003 - Update PDF.js to 57334bd20544a713c400476e7bf966491dd1c823 r=calixte

Differential Revision: https://phabricator.services.mozilla.com/D271816

Diffstat:
Mtoolkit/components/pdfjs/content/build/pdf.mjs | 18+++++++++++++-----
Mtoolkit/components/pdfjs/content/build/pdf.scripting.mjs | 4++--
Mtoolkit/components/pdfjs/content/build/pdf.worker.mjs | 791++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mtoolkit/components/pdfjs/content/web/viewer-geckoview.mjs | 6+++---
Mtoolkit/components/pdfjs/content/web/viewer.mjs | 6+++---
Mtoolkit/components/pdfjs/moz.yaml | 4++--
6 files changed, 766 insertions(+), 63 deletions(-)

diff --git a/toolkit/components/pdfjs/content/build/pdf.mjs b/toolkit/components/pdfjs/content/build/pdf.mjs @@ -21,8 +21,8 @@ */ /** - * pdfjsVersion = 5.4.396 - * pdfjsBuild = 0a2680bca + * pdfjsVersion = 5.4.402 + * pdfjsBuild = 57334bd20 */ /******/ // The require scope /******/ var __webpack_require__ = {}; @@ -13083,7 +13083,7 @@ function getDocument(src = {}) { } const docParams = { docId, - apiVersion: "5.4.396", + apiVersion: "5.4.402", data, password, disableAutoFetch, @@ -13329,6 +13329,9 @@ class PDFDocumentProxy { saveDocument() { return this._transport.saveDocument(); } + extractPages(pageInfos) { + return this._transport.extractPages(pageInfos); + } getDownloadInfo() { return this._transport.downloadInfoCapability.promise; } @@ -14333,6 +14336,11 @@ class WorkerTransport { this.annotationStorage.resetModified(); }); } + extractPages(pageInfos) { + return this.messageHandler.sendWithPromise("ExtractPages", { + pageInfos + }); + } getPage(pageNumber) { if (!Number.isInteger(pageNumber) || pageNumber <= 0 || pageNumber > this._numPages) { return Promise.reject(new Error("Invalid page request.")); @@ -14663,8 +14671,8 @@ class InternalRenderTask { } } } -const version = "5.4.396"; -const build = "0a2680bca"; +const version = "5.4.402"; +const build = "57334bd20"; ;// ./src/display/editor/color_picker.js diff --git a/toolkit/components/pdfjs/content/build/pdf.scripting.mjs b/toolkit/components/pdfjs/content/build/pdf.scripting.mjs @@ -21,8 +21,8 @@ */ /** - * pdfjsVersion = 5.4.396 - * pdfjsBuild = 0a2680bca + * pdfjsVersion = 5.4.402 + * pdfjsBuild = 57334bd20 */ var __webpack_exports__ = {}; diff --git a/toolkit/components/pdfjs/content/build/pdf.worker.mjs b/toolkit/components/pdfjs/content/build/pdf.worker.mjs @@ -21,8 +21,8 @@ */ /** - * pdfjsVersion = 5.4.396 - * pdfjsBuild = 0a2680bca + * pdfjsVersion = 5.4.402 + * pdfjsBuild = 57334bd20 */ /******/ // The require scope /******/ var __webpack_require__ = {}; @@ -1024,6 +1024,9 @@ class Dict { getRawValues() { return [...this._map.values()]; } + getRawEntries() { + return this._map.entries(); + } set(key, value) { this._map.set(key, value); } @@ -1054,6 +1057,11 @@ class Dict { this.set(key, value); } } + setIfDict(key, value) { + if (value instanceof Dict) { + this.set(key, value); + } + } has(key) { return this._map.has(key); } @@ -2893,6 +2901,9 @@ class Stream extends BaseStream { makeSubStream(start, length, dict = null) { return new Stream(this.bytes.buffer, start, length, dict); } + clone() { + return new Stream(this.bytes.buffer, this.start, this.end - this.start, this.dict.clone()); + } } class StringStream extends Stream { constructor(str) { @@ -3910,6 +3921,12 @@ class DecodeStream extends BaseStream { getBaseStreams() { return this.stream ? this.stream.getBaseStreams() : null; } + clone() { + while (!this.eof) { + this.readBlock(); + } + return new Stream(this.buffer, this.start, this.end - this.start, this.dict.clone()); + } } class StreamsSequenceStream extends DecodeStream { constructor(streams, onError = null) { @@ -37072,6 +37089,7 @@ class StructTreeRoot { this.roleMap = new Map(); this.structParentIds = null; this.kidRefToPosition = undefined; + this.parentTree = null; } getKidPosition(kidRef) { if (this.kidRefToPosition === undefined) { @@ -37096,6 +37114,11 @@ class StructTreeRoot { } init() { this.readRoleMap(); + const parentTree = this.dict.get("ParentTree"); + if (!parentTree) { + return; + } + this.parentTree = new NumberTree(parentTree, this.xref); } #addIdToPage(pageRef, id, type) { if (!(pageRef instanceof Ref) || id < 0) { @@ -37718,7 +37741,9 @@ class StructTreePage { if (!this.root || !this.rootDict || !(pageRef instanceof Ref)) { return; } - const parentTree = this.rootDict.get("ParentTree"); + const { + parentTree + } = this.root; if (!parentTree) { return; } @@ -37728,9 +37753,8 @@ class StructTreePage { return; } const map = new Map(); - const numberTree = new NumberTree(parentTree, this.xref); if (Number.isInteger(id)) { - const parentArray = numberTree.get(id); + const parentArray = parentTree.get(id); if (Array.isArray(parentArray)) { for (const ref of parentArray) { if (ref instanceof Ref) { @@ -37743,7 +37767,7 @@ class StructTreePage { return; } for (const [elemId, type] of ids) { - const obj = numberTree.get(elemId); + const obj = parentTree.get(elemId); if (obj) { const elem = this.addNode(this.xref.fetchIfRef(obj), map); if (elem?.kids?.length === 1 && elem.kids[0].type === StructElementType.OBJECT) { @@ -38472,6 +38496,14 @@ class Catalog { } return rawDests; } + get rawPageLabels() { + const obj = this.#catDict.getRaw("PageLabels"); + if (!obj) { + return null; + } + const numberTree = new NumberTree(obj, this.xref); + return numberTree.getAll(); + } get pageLabels() { let obj = null; try { @@ -38485,15 +38517,13 @@ class Catalog { return shadow(this, "pageLabels", obj); } #readPageLabels() { - const obj = this.#catDict.getRaw("PageLabels"); - if (!obj) { + const nums = this.rawPageLabels; + if (!nums) { return null; } const pageLabels = new Array(this.numPages); let style = null, prefix = ""; - const numberTree = new NumberTree(obj, this.xref); - const nums = numberTree.getAll(); let currentLabel = "", currentIndex = 1; for (let i = 0, ii = this.numPages; i < ii; i++) { @@ -54333,6 +54363,9 @@ class DecryptStream extends DecodeStream { buffer.set(chunk, bufferLength); this.bufferLength = newLength; } + getOriginalStream() { + return this; + } } ;// ./src/core/crypto.js @@ -55966,7 +55999,7 @@ class Page { const resources = this.#getInheritableProperty("Resources"); return shadow(this, "resources", resources instanceof Dict ? resources : Dict.empty); } - #getBoundingBox(name) { + getBoundingBox(name) { if (this.xfaData) { return this.xfaData.bbox; } @@ -55980,10 +56013,10 @@ class Page { return null; } get mediaBox() { - return shadow(this, "mediaBox", this.#getBoundingBox("MediaBox") || LETTER_SIZE_MEDIABOX); + return shadow(this, "mediaBox", this.getBoundingBox("MediaBox") || LETTER_SIZE_MEDIABOX); } get cropBox() { - return shadow(this, "cropBox", this.#getBoundingBox("CropBox") || this.mediaBox); + return shadow(this, "cropBox", this.getBoundingBox("CropBox") || this.mediaBox); } get userUnit() { const obj = this.pageDict.get("UserUnit"); @@ -57840,28 +57873,25 @@ class MessageHandler { async function writeObject(ref, obj, buffer, { - encrypt = null + encrypt = null, + encryptRef = null }) { - const transform = encrypt?.createCipherTransform(ref.num, ref.gen); + const transform = encrypt && encryptRef !== ref ? encrypt.createCipherTransform(ref.num, ref.gen) : null; buffer.push(`${ref.num} ${ref.gen} obj\n`); - if (obj instanceof Dict) { - await writeDict(obj, buffer, transform); - } else if (obj instanceof BaseStream) { - await writeStream(obj, buffer, transform); - } else if (Array.isArray(obj) || ArrayBuffer.isView(obj)) { - await writeArray(obj, buffer, transform); - } + await writeValue(obj, buffer, transform); buffer.push("\nendobj\n"); } async function writeDict(dict, buffer, transform) { buffer.push("<<"); - for (const key of dict.getKeys()) { + for (const [key, rawObj] of dict.getRawEntries()) { buffer.push(` /${escapePDFName(key)} `); - await writeValue(dict.getRaw(key), buffer, transform); + await writeValue(rawObj, buffer, transform); } buffer.push(">>"); } async function writeStream(stream, buffer, transform) { + stream = stream.getOriginalStream(); + stream.reset(); let bytes = stream.getBytes(); const { dict @@ -57870,7 +57900,7 @@ async function writeStream(stream, buffer, transform) { const filterZero = Array.isArray(filter) ? await dict.xref.fetchIfRefAsync(filter[0]) : filter; const isFilterZeroFlateDecode = isName(filterZero, "FlateDecode"); const MIN_LENGTH_FOR_COMPRESSING = 256; - if (bytes.length >= MIN_LENGTH_FOR_COMPRESSING || isFilterZeroFlateDecode) { + if (bytes.length >= MIN_LENGTH_FOR_COMPRESSING && !isFilterZeroFlateDecode) { try { const cs = new CompressionStream("deflate"); const writer = cs.writable.getWriter(); @@ -57910,14 +57940,11 @@ async function writeStream(stream, buffer, transform) { } async function writeArray(array, buffer, transform) { buffer.push("["); - let first = true; - for (const val of array) { - if (!first) { + for (let i = 0, ii = array.length; i < ii; i++) { + await writeValue(array[i], buffer, transform); + if (i < ii - 1) { buffer.push(" "); - } else { - first = false; } - await writeValue(val, buffer, transform); } buffer.push("]"); } @@ -57934,7 +57961,7 @@ async function writeValue(value, buffer, transform) { } buffer.push(`(${escapeString(value)})`); } else if (typeof value === "number") { - buffer.push(numberToString(value)); + buffer.push(value.toString()); } else if (typeof value === "boolean") { buffer.push(value.toString()); } else if (value instanceof Dict) { @@ -58073,7 +58100,7 @@ async function getXRefTable(xrefInfo, baseOffset, newRefs, newXref, buffer) { } computeIDs(baseOffset, xrefInfo, newXref); buffer.push("trailer\n"); - await writeDict(newXref, buffer); + await writeDict(newXref, buffer, null); buffer.push("\nstartxref\n", baseOffset.toString(), "\n%%EOF\n"); } function getIndexes(newRefs) { @@ -58095,11 +58122,16 @@ async function getXRefStreamTable(xrefInfo, baseOffset, newRefs, newXref, buffer let maxGen = 0; for (const { ref, - data + data, + objStreamRef, + index } of newRefs) { let gen; maxOffset = Math.max(maxOffset, baseOffset); - if (data !== null) { + if (objStreamRef) { + gen = index; + xrefTableData.push([2, objStreamRef.num, gen]); + } else if (data !== null) { gen = Math.min(ref.gen, 0xffff); xrefTableData.push([1, baseOffset, gen]); baseOffset += data.length; @@ -58131,12 +58163,12 @@ async function getXRefStreamTable(xrefInfo, baseOffset, newRefs, newXref, buffer function computeIDs(baseOffset, xrefInfo, newXref) { if (Array.isArray(xrefInfo.fileIds) && xrefInfo.fileIds.length > 0) { const md5 = computeMD5(baseOffset, xrefInfo); - newXref.set("ID", [xrefInfo.fileIds[0], md5]); + newXref.set("ID", [xrefInfo.fileIds[0] || md5, md5]); } } function getTrailerDict(xrefInfo, changes, useXrefStream) { const newXref = new Dict(null); - newXref.set("Prev", xrefInfo.startXRef); + newXref.setIfDefined("Prev", xrefInfo?.startXRef); const refForXrefTable = xrefInfo.newRef; if (useXrefStream) { changes.put(refForXrefTable, { @@ -58147,22 +58179,27 @@ function getTrailerDict(xrefInfo, changes, useXrefStream) { } else { newXref.set("Size", refForXrefTable.num); } - if (xrefInfo.rootRef !== null) { - newXref.set("Root", xrefInfo.rootRef); - } - if (xrefInfo.infoRef !== null) { - newXref.set("Info", xrefInfo.infoRef); - } - if (xrefInfo.encryptRef !== null) { - newXref.set("Encrypt", xrefInfo.encryptRef); - } + newXref.setIfDefined("Root", xrefInfo?.rootRef); + newXref.setIfDefined("Info", xrefInfo?.infoRef); + newXref.setIfDefined("Encrypt", xrefInfo?.encryptRef); return newXref; } async function writeChanges(changes, xref, buffer = []) { const newRefs = []; for (const [ref, { - data + data, + objStreamRef, + index }] of changes.items()) { + if (objStreamRef) { + newRefs.push({ + ref, + data, + objStreamRef, + index + }); + continue; + } if (data === null || typeof data === "string") { newRefs.push({ ref, @@ -58238,6 +58275,577 @@ async function incrementalUpdate({ return array; } +;// ./src/core/editor/pdf_editor.js + + + + + + +const MAX_LEAVES_PER_PAGES_NODE = 16; +const MAX_IN_NAME_TREE_NODE = 64; +class PageData { + constructor(page, documentData) { + this.page = page; + this.documentData = documentData; + this.annotations = null; + documentData.pagesMap.put(page.ref, this); + } +} +class DocumentData { + constructor(document) { + this.document = document; + this.pageLabels = null; + this.pagesMap = new RefSetCache(); + this.oldRefMapping = new RefSetCache(); + } +} +class PDFEditor { + constructor({ + useObjectStreams = true, + title = "", + author = "" + } = {}) { + this.hasSingleFile = false; + this.currentDocument = null; + this.oldPages = []; + this.newPages = []; + this.xref = [null]; + this.newRefCount = 1; + [this.rootRef, this.rootDict] = this.newDict; + [this.infoRef, this.infoDict] = this.newDict; + [this.pagesRef, this.pagesDict] = this.newDict; + this.namesDict = null; + this.useObjectStreams = useObjectStreams; + this.objStreamRefs = useObjectStreams ? new Set() : null; + this.version = "1.7"; + this.title = title; + this.author = author; + this.pageLabels = null; + } + get newRef() { + const ref = Ref.get(this.newRefCount++, 0); + return ref; + } + get newDict() { + const ref = this.newRef; + const dict = this.xref[ref.num] = new Dict(); + return [ref, dict]; + } + async #cloneObject(obj, xref) { + const ref = this.newRef; + this.xref[ref.num] = await this.#collectDependencies(obj, true, xref); + return ref; + } + async #collectDependencies(obj, mustClone, xref) { + if (obj instanceof Ref) { + const { + currentDocument: { + oldRefMapping + } + } = this; + let newRef = oldRefMapping.get(obj); + if (newRef) { + return newRef; + } + newRef = this.newRef; + oldRefMapping.put(obj, newRef); + obj = await xref.fetchAsync(obj); + this.xref[newRef.num] = await this.#collectDependencies(obj, true, xref); + return newRef; + } + const promises = []; + if (Array.isArray(obj)) { + if (mustClone) { + obj = obj.slice(); + } + for (let i = 0, ii = obj.length; i < ii; i++) { + promises.push(this.#collectDependencies(obj[i], true, xref).then(newObj => obj[i] = newObj)); + } + await Promise.all(promises); + return obj; + } + let dict; + if (obj instanceof BaseStream) { + ({ + dict + } = obj = obj.getOriginalStream().clone()); + } else if (obj instanceof Dict) { + if (mustClone) { + obj = obj.clone(); + } + dict = obj; + } + if (dict) { + for (const [key, rawObj] of dict.getRawEntries()) { + promises.push(this.#collectDependencies(rawObj, true, xref).then(newObj => dict.set(key, newObj))); + } + await Promise.all(promises); + } + return obj; + } + async extractPages(pageInfos) { + const promises = []; + let newIndex = 0; + this.hasSingleFile = pageInfos.length === 1; + for (const { + document, + includePages, + excludePages + } of pageInfos) { + if (!document) { + continue; + } + const documentData = new DocumentData(document); + promises.push(this.#collectDocumentData(documentData)); + let keptIndices, keptRanges, deletedIndices, deletedRanges; + for (const page of includePages || []) { + if (Array.isArray(page)) { + (keptRanges ||= []).push(page); + } else { + (keptIndices ||= new Set()).add(page); + } + } + for (const page of excludePages || []) { + if (Array.isArray(page)) { + (deletedRanges ||= []).push(page); + } else { + (deletedIndices ||= new Set()).add(page); + } + } + for (let i = 0, ii = document.numPages; i < ii; i++) { + if (deletedIndices?.has(i)) { + continue; + } + if (deletedRanges) { + let isDeleted = false; + for (const [start, end] of deletedRanges) { + if (i >= start && i <= end) { + isDeleted = true; + break; + } + } + if (isDeleted) { + continue; + } + } + let takePage = false; + if (keptIndices) { + takePage = keptIndices.has(i); + } + if (!takePage && keptRanges) { + for (const [start, end] of keptRanges) { + if (i >= start && i <= end) { + takePage = true; + break; + } + } + } + if (!takePage && !keptIndices && !keptRanges) { + takePage = true; + } + if (!takePage) { + continue; + } + const newPageIndex = newIndex++; + promises.push(document.getPage(i).then(page => { + this.oldPages[newPageIndex] = new PageData(page, documentData); + })); + } + } + await Promise.all(promises); + promises.length = 0; + this.#collectPageLabels(); + for (const page of this.oldPages) { + promises.push(this.#postCollectPageData(page)); + } + await Promise.all(promises); + for (let i = 0, ii = this.oldPages.length; i < ii; i++) { + this.newPages[i] = await this.#makePageCopy(i, null); + } + return this.writePDF(); + } + async #collectDocumentData(documentData) { + const { + document + } = documentData; + await document.pdfManager.ensureCatalog("rawPageLabels").then(pageLabels => documentData.pageLabels = pageLabels); + } + async #postCollectPageData(pageData) { + const { + page: { + xref, + annotations + } + } = pageData; + if (!annotations) { + return; + } + const promises = []; + let newAnnotations = []; + let newIndex = 0; + for (const annotationRef of annotations) { + const newAnnotationIndex = newIndex++; + promises.push(xref.fetchIfRefAsync(annotationRef).then(async annotationDict => { + if (!isName(annotationDict.get("Subtype"), "Link")) { + newAnnotations[newAnnotationIndex] = annotationRef; + } + })); + } + await Promise.all(promises); + newAnnotations = newAnnotations.filter(annot => !!annot); + pageData.annotations = newAnnotations.length > 0 ? newAnnotations : null; + } + async #collectPageLabels() { + if (!this.hasSingleFile) { + return; + } + const { + documentData: { + document, + pageLabels + } + } = this.oldPages[0]; + if (!pageLabels) { + return; + } + const numPages = document.numPages; + const oldPageLabels = []; + const oldPageIndices = new Set(this.oldPages.map(({ + page: { + pageIndex + } + }) => pageIndex)); + let currentLabel = null; + let stFirstIndex = -1; + for (let i = 0; i < numPages; i++) { + const newLabel = pageLabels.get(i); + if (newLabel) { + currentLabel = newLabel; + stFirstIndex = currentLabel.has("St") ? i : -1; + } + if (!oldPageIndices.has(i)) { + continue; + } + if (stFirstIndex !== -1) { + const st = currentLabel.get("St"); + currentLabel = currentLabel.clone(); + currentLabel.set("St", st + (i - stFirstIndex)); + stFirstIndex = -1; + } + oldPageLabels.push(currentLabel); + } + currentLabel = oldPageLabels[0]; + let currentIndex = 0; + const newPageLabels = this.pageLabels = [[0, currentLabel]]; + for (let i = 0, ii = oldPageLabels.length; i < ii; i++) { + const label = oldPageLabels[i]; + if (label === currentLabel) { + continue; + } + currentIndex = i; + currentLabel = label; + newPageLabels.push([currentIndex, currentLabel]); + } + } + async #makePageCopy(pageIndex) { + const { + page, + documentData, + annotations + } = this.oldPages[pageIndex]; + this.currentDocument = documentData; + const { + oldRefMapping + } = documentData; + const { + xref, + rotate, + mediaBox, + resources, + ref: oldPageRef + } = page; + const pageRef = this.newRef; + const pageDict = this.xref[pageRef.num] = page.pageDict.clone(); + oldRefMapping.put(oldPageRef, pageRef); + for (const key of ["Rotate", "MediaBox", "CropBox", "BleedBox", "TrimBox", "ArtBox", "Resources", "Annots", "Parent", "UserUnit"]) { + pageDict.delete(key); + } + const lastRef = this.newRefCount; + await this.#collectDependencies(pageDict, false, xref); + pageDict.set("Rotate", rotate); + pageDict.set("MediaBox", mediaBox); + for (const boxName of ["CropBox", "BleedBox", "TrimBox", "ArtBox"]) { + const box = page.getBoundingBox(boxName); + if (box?.some((value, index) => value !== mediaBox[index])) { + pageDict.set(boxName, box); + } + } + const userUnit = page.userUnit; + if (userUnit !== 1) { + pageDict.set("UserUnit", userUnit); + } + pageDict.setIfDict("Resources", await this.#collectDependencies(resources, true, xref)); + pageDict.setIfArray("Annots", await this.#collectDependencies(annotations, true, xref)); + if (this.useObjectStreams) { + const newLastRef = this.newRefCount; + const pageObjectRefs = []; + for (let i = lastRef; i < newLastRef; i++) { + const obj = this.xref[i]; + if (obj instanceof BaseStream) { + continue; + } + pageObjectRefs.push(Ref.get(i, 0)); + } + for (let i = 0; i < pageObjectRefs.length; i += 0xffff) { + const objStreamRef = this.newRef; + this.objStreamRefs.add(objStreamRef.num); + this.xref[objStreamRef.num] = pageObjectRefs.slice(i, i + 0xffff); + } + } + this.currentDocument = null; + return pageRef; + } + #makePageTree() { + const { + newPages: pages, + rootDict, + pagesRef, + pagesDict + } = this; + rootDict.set("Pages", pagesRef); + pagesDict.setIfName("Type", "Pages"); + pagesDict.set("Count", pages.length); + const maxLeaves = MAX_LEAVES_PER_PAGES_NODE <= 1 ? pages.length : MAX_LEAVES_PER_PAGES_NODE; + const stack = [{ + dict: pagesDict, + kids: pages, + parentRef: pagesRef + }]; + while (stack.length > 0) { + const { + dict, + kids, + parentRef + } = stack.pop(); + if (kids.length <= maxLeaves) { + dict.set("Kids", kids); + for (const ref of kids) { + this.xref[ref.num].set("Parent", parentRef); + } + continue; + } + const chunkSize = Math.max(maxLeaves, Math.ceil(kids.length / maxLeaves)); + const kidsChunks = []; + for (let i = 0; i < kids.length; i += chunkSize) { + kidsChunks.push(kids.slice(i, i + chunkSize)); + } + const kidsRefs = []; + dict.set("Kids", kidsRefs); + for (const chunk of kidsChunks) { + const [kidRef, kidDict] = this.newDict; + kidsRefs.push(kidRef); + kidDict.setIfName("Type", "Pages"); + kidDict.set("Parent", parentRef); + kidDict.set("Count", chunk.length); + stack.push({ + dict: kidDict, + kids: chunk, + parentRef: kidRef + }); + } + } + } + #makeNameNumTree(map, areNames) { + const allEntries = map.sort(areNames ? ([keyA], [keyB]) => keyA.localeCompare(keyB) : ([keyA], [keyB]) => keyA - keyB); + const maxLeaves = MAX_IN_NAME_TREE_NODE <= 1 ? allEntries.length : MAX_IN_NAME_TREE_NODE; + const [treeRef, treeDict] = this.newDict; + const stack = [{ + dict: treeDict, + entries: allEntries + }]; + const valueType = areNames ? "Names" : "Nums"; + while (stack.length > 0) { + const { + dict, + entries + } = stack.pop(); + if (entries.length <= maxLeaves) { + dict.set("Limits", [entries[0][0], entries.at(-1)[0]]); + dict.set(valueType, entries.flat()); + continue; + } + const entriesChunks = []; + const chunkSize = Math.max(maxLeaves, Math.ceil(entries.length / maxLeaves)); + for (let i = 0; i < entries.length; i += chunkSize) { + entriesChunks.push(entries.slice(i, i + chunkSize)); + } + const entriesRefs = []; + dict.set("Kids", entriesRefs); + for (const chunk of entriesChunks) { + const [entriesRef, entriesDict] = this.newDict; + entriesRefs.push(entriesRef); + entriesDict.set("Limits", [chunk[0][0], chunk.at(-1)[0]]); + stack.push({ + dict: entriesDict, + entries: chunk + }); + } + } + return treeRef; + } + #makePageLabelsTree() { + const { + pageLabels + } = this; + if (!pageLabels || pageLabels.length === 0) { + return; + } + const { + rootDict + } = this; + const pageLabelsRef = this.#makeNameNumTree(this.pageLabels, false); + rootDict.set("PageLabels", pageLabelsRef); + } + async #makeRoot() { + const { + rootDict + } = this; + rootDict.setIfName("Type", "Catalog"); + rootDict.set("Version", this.version); + this.#makePageTree(); + this.#makePageLabelsTree(); + } + #makeInfo() { + const infoMap = new Map(); + if (this.hasSingleFile) { + const { + xref: { + trailer + } + } = this.oldPages[0].documentData.document; + const oldInfoDict = trailer.get("Info"); + for (const [key, value] of oldInfoDict || []) { + if (typeof value === "string") { + infoMap.set(key, stringToPDFString(value)); + } + } + } + infoMap.delete("ModDate"); + infoMap.set("CreationDate", getModificationDate()); + infoMap.set("Creator", "PDF.js"); + infoMap.set("Producer", "Firefox"); + if (this.author) { + infoMap.set("Author", this.author); + } + if (this.title) { + infoMap.set("Title", this.title); + } + for (const [key, value] of infoMap) { + this.infoDict.set(key, stringToAsciiOrUTF16BE(value)); + } + return infoMap; + } + async #makeEncrypt() { + if (!this.hasSingleFile) { + return [null, null, null]; + } + const { + documentData + } = this.oldPages[0]; + const { + document: { + xref: { + trailer, + encrypt + } + } + } = documentData; + if (!trailer.has("Encrypt")) { + return [null, null, null]; + } + const encryptDict = trailer.get("Encrypt"); + if (!(encryptDict instanceof Dict)) { + return [null, null, null]; + } + this.currentDocument = documentData; + const result = [await this.#cloneObject(encryptDict, trailer.xref), encrypt, trailer.get("ID")]; + this.currentDocument = null; + return result; + } + async #createChanges() { + const changes = new RefSetCache(); + changes.put(Ref.get(0, 0xffff), { + data: null + }); + for (let i = 1, ii = this.xref.length; i < ii; i++) { + if (this.objStreamRefs?.has(i)) { + await this.#createObjectStream(Ref.get(i, 0), this.xref[i], changes); + } else { + changes.put(Ref.get(i, 0), { + data: this.xref[i] + }); + } + } + return [changes, this.newRef]; + } + async #createObjectStream(objStreamRef, objRefs, changes) { + const streamBuffer = [""]; + const objOffsets = []; + let offset = 0; + const buffer = []; + for (let i = 0, ii = objRefs.length; i < ii; i++) { + const objRef = objRefs[i]; + changes.put(objRef, { + data: null, + objStreamRef, + index: i + }); + objOffsets.push(`${objRef.num} ${offset}`); + const data = this.xref[objRef.num]; + await writeValue(data, buffer, null); + const obj = buffer.join(""); + buffer.length = 0; + streamBuffer.push(obj); + offset += obj.length + 1; + } + streamBuffer[0] = objOffsets.join("\n"); + const objStream = new StringStream(streamBuffer.join("\n")); + const objStreamDict = objStream.dict = new Dict(); + objStreamDict.setIfName("Type", "ObjStm"); + objStreamDict.set("N", objRefs.length); + objStreamDict.set("First", streamBuffer[0].length + 1); + changes.put(objStreamRef, { + data: objStream + }); + } + async writePDF() { + await this.#makeRoot(); + const infoMap = this.#makeInfo(); + const [encryptRef, encrypt, fileIds] = await this.#makeEncrypt(); + const [changes, xrefTableRef] = await this.#createChanges(); + const header = [...`%PDF-${this.version}\n%`.split("").map(c => c.charCodeAt(0)), 0xfa, 0xde, 0xfa, 0xce]; + return incrementalUpdate({ + originalData: new Uint8Array(header), + changes, + xrefInfo: { + startXRef: null, + rootRef: this.rootRef, + infoRef: this.infoRef, + encryptRef, + newRef: xrefTableRef, + fileIds: fileIds || [null, null], + infoMap + }, + useXrefStream: this.useObjectStreams, + xref: { + encrypt, + encryptRef + } + }); + } +} + ;// ./src/core/worker_stream.js class PDFWorkerStream { @@ -58356,6 +58964,7 @@ class PDFWorkerStreamRangeReader { + class WorkerTask { constructor(name) { this.name = name; @@ -58407,7 +59016,7 @@ class WorkerMessageHandler { docId, apiVersion } = docParams; - const workerVersion = "5.4.396"; + const workerVersion = "5.4.402"; if (apiVersion !== workerVersion) { throw new Error(`The API version "${apiVersion}" does not match ` + `the Worker version "${workerVersion}".`); } @@ -58726,6 +59335,92 @@ class WorkerMessageHandler { handler.on("GetCalculationOrderIds", function (data) { return pdfManager.ensureDoc("calculationOrderIds"); }); + handler.on("ExtractPages", async function ({ + pageInfos + }) { + if (!pageInfos) { + warn("extractPages: nothing to extract."); + return null; + } + if (!Array.isArray(pageInfos)) { + pageInfos = [pageInfos]; + } + let newDocumentId = 0; + for (const pageInfo of pageInfos) { + if (pageInfo.document === null) { + pageInfo.document = pdfManager.pdfDocument; + } else if (ArrayBuffer.isView(pageInfo.document)) { + const manager = new LocalPdfManager({ + source: pageInfo.document, + docId: `${docId}_extractPages_${newDocumentId++}`, + handler, + password: pageInfo.password ?? null, + evaluatorOptions: Object.assign({}, pdfManager.evaluatorOptions) + }); + let recoveryMode = false; + let isValid = true; + while (true) { + try { + await manager.requestLoadedStream(); + await manager.ensureDoc("checkHeader"); + await manager.ensureDoc("parseStartXRef"); + await manager.ensureDoc("parse", [recoveryMode]); + break; + } catch (e) { + if (e instanceof XRefParseException) { + if (recoveryMode === false) { + recoveryMode = true; + continue; + } else { + isValid = false; + warn("extractPages: XRefParseException."); + } + } else if (e instanceof PasswordException) { + const task = new WorkerTask(`PasswordException: response ${e.code}`); + startWorkerTask(task); + try { + const { + password + } = await handler.sendWithPromise("PasswordRequest", e); + manager.updatePassword(password); + } catch { + isValid = false; + warn("extractPages: invalid password."); + } finally { + finishWorkerTask(task); + } + } else { + isValid = false; + warn("extractPages: invalid document."); + } + if (!isValid) { + break; + } + } + } + if (!isValid) { + pageInfo.document = null; + } + const isPureXfa = await manager.ensureDoc("isPureXfa"); + if (isPureXfa) { + pageInfo.document = null; + warn("extractPages does not support pure XFA documents."); + } else { + pageInfo.document = manager.pdfDocument; + } + } else { + warn("extractPages: invalid document."); + } + } + try { + const pdfEditor = new PDFEditor(); + const buffer = await pdfEditor.extractPages(pageInfos); + return buffer; + } catch (reason) { + console.error(reason); + return null; + } + }); handler.on("SaveDocument", async function ({ isPureXfa, numPages, diff --git a/toolkit/components/pdfjs/content/web/viewer-geckoview.mjs b/toolkit/components/pdfjs/content/web/viewer-geckoview.mjs @@ -21,8 +21,8 @@ */ /** - * pdfjsVersion = 5.4.396 - * pdfjsBuild = 0a2680bca + * pdfjsVersion = 5.4.402 + * pdfjsBuild = 57334bd20 */ /******/ // The require scope /******/ var __webpack_require__ = {}; @@ -8300,7 +8300,7 @@ class PDFViewer { #textLayerMode = TextLayerMode.ENABLE; #viewerAlert = null; constructor(options) { - const viewerVersion = "5.4.396"; + const viewerVersion = "5.4.402"; if (version !== viewerVersion) { throw new Error(`The API version "${version}" does not match the Viewer version "${viewerVersion}".`); } diff --git a/toolkit/components/pdfjs/content/web/viewer.mjs b/toolkit/components/pdfjs/content/web/viewer.mjs @@ -21,8 +21,8 @@ */ /** - * pdfjsVersion = 5.4.396 - * pdfjsBuild = 0a2680bca + * pdfjsVersion = 5.4.402 + * pdfjsBuild = 57334bd20 */ /******/ // The require scope /******/ var __webpack_require__ = {}; @@ -11485,7 +11485,7 @@ class PDFViewer { #textLayerMode = TextLayerMode.ENABLE; #viewerAlert = null; constructor(options) { - const viewerVersion = "5.4.396"; + const viewerVersion = "5.4.402"; if (version !== viewerVersion) { throw new Error(`The API version "${version}" does not match the Viewer version "${viewerVersion}".`); } diff --git a/toolkit/components/pdfjs/moz.yaml b/toolkit/components/pdfjs/moz.yaml @@ -20,8 +20,8 @@ origin: # Human-readable identifier for this version/release # Generally "version NNN", "tag SSS", "bookmark SSS" - release: 0a2680bca627243d157e18a52189cc19b635993e (2025-11-02T12:14:45Z). - revision: 0a2680bca627243d157e18a52189cc19b635993e + release: 57334bd20544a713c400476e7bf966491dd1c823 (2025-11-07T16:21:38Z). + revision: 57334bd20544a713c400476e7bf966491dd1c823 # The package's license, where possible using the mnemonic from # https://spdx.org/licenses/