diff options
Diffstat (limited to 'toolkit/components/pdfjs')
26 files changed, 1692 insertions, 727 deletions
diff --git a/toolkit/components/pdfjs/content/GeckoViewPdfjsChild.sys.mjs b/toolkit/components/pdfjs/content/GeckoViewPdfjsChild.sys.mjs index e838c67874..749532053b 100644 --- a/toolkit/components/pdfjs/content/GeckoViewPdfjsChild.sys.mjs +++ b/toolkit/components/pdfjs/content/GeckoViewPdfjsChild.sys.mjs @@ -17,9 +17,7 @@ import { GeckoViewActorChild } from "resource://gre/modules/GeckoViewActorChild. export class GeckoViewPdfjsChild extends GeckoViewActorChild { init(aSupportsFind) { - if (aSupportsFind) { - this.sendAsyncMessage("PDFJS:Parent:addEventListener"); - } + this.sendAsyncMessage("PDFJS:Parent:addEventListener", { aSupportsFind }); } dispatchEvent(aType, aDetail) { diff --git a/toolkit/components/pdfjs/content/GeckoViewPdfjsParent.sys.mjs b/toolkit/components/pdfjs/content/GeckoViewPdfjsParent.sys.mjs index b07ed8c7b1..e27fdac51d 100644 --- a/toolkit/components/pdfjs/content/GeckoViewPdfjsParent.sys.mjs +++ b/toolkit/components/pdfjs/content/GeckoViewPdfjsParent.sys.mjs @@ -258,7 +258,7 @@ export class GeckoViewPdfjsParent extends GeckoViewActorParent { case "PDFJS:Parent:updateMatchesCount": return this.#updateMatchesCount(aMsg); case "PDFJS:Parent:addEventListener": - return this.#addEventListener(); + return this.#addEventListener(aMsg); case "PDFJS:Parent:saveURL": return this.#save(aMsg); case "PDFJS:Parent:getNimbus": @@ -299,7 +299,16 @@ export class GeckoViewPdfjsParent extends GeckoViewActorParent { this.#fileSaver = null; } - #addEventListener() { + #addEventListener({ data: { aSupportsFind } }) { + this.#fileSaver = new FileSaver(this.browser, this.eventDispatcher); + this.eventDispatcher.registerListener(this.#fileSaver, [ + "GeckoView:PDFSave", + ]); + + if (!aSupportsFind) { + return; + } + if (this.#findHandler) { this.#findHandler.cleanup(); return; @@ -311,11 +320,6 @@ export class GeckoViewPdfjsParent extends GeckoViewActorParent { "GeckoView:DisplayMatches", "GeckoView:FindInPage", ]); - - this.#fileSaver = new FileSaver(this.browser, this.eventDispatcher); - this.eventDispatcher.registerListener(this.#fileSaver, [ - "GeckoView:PDFSave", - ]); } #updateMatchesCount({ data }) { diff --git a/toolkit/components/pdfjs/content/PdfJsDefaultPreferences.sys.mjs b/toolkit/components/pdfjs/content/PdfJsDefaultPreferences.sys.mjs index 02d1095919..ee30631bff 100644 --- a/toolkit/components/pdfjs/content/PdfJsDefaultPreferences.sys.mjs +++ b/toolkit/components/pdfjs/content/PdfJsDefaultPreferences.sys.mjs @@ -25,6 +25,7 @@ export const PdfJsDefaultPreferences = Object.freeze({ defaultZoomValue: "", disablePageLabels: false, enableHighlightEditor: false, + enableHighlightFloatingButton: false, enableML: false, enablePermissions: false, enablePrintAutoRotate: true, diff --git a/toolkit/components/pdfjs/content/PdfStreamConverter.sys.mjs b/toolkit/components/pdfjs/content/PdfStreamConverter.sys.mjs index 050d730872..2a56cbb935 100644 --- a/toolkit/components/pdfjs/content/PdfStreamConverter.sys.mjs +++ b/toolkit/components/pdfjs/content/PdfStreamConverter.sys.mjs @@ -19,6 +19,9 @@ const PDF_VIEWER_WEB_PAGE = "resource://pdf.js/web/viewer.html"; const MAX_NUMBER_OF_PREFS = 50; const PDF_CONTENT_TYPE = "application/pdf"; +// Preferences +const caretBrowsingModePref = "accessibility.browsewithcaret"; + import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; @@ -178,6 +181,45 @@ PdfDataListener.prototype = { }, }; +class PrefObserver { + #domWindow; + + constructor(domWindow) { + this.#domWindow = domWindow; + this.#init(); + } + + #init() { + Services.prefs.addObserver( + caretBrowsingModePref, + this, + /* aHoldWeak = */ true + ); + } + + observe(_aSubject, aTopic, aPrefName) { + if (aTopic != "nsPref:changed") { + return; + } + + const actor = getActor(this.#domWindow); + if (!actor) { + return; + } + const eventName = "updatedPreference"; + switch (aPrefName) { + case caretBrowsingModePref: + actor.dispatchEvent(eventName, { + name: "supportsCaretBrowsingMode", + value: Services.prefs.getBoolPref(caretBrowsingModePref), + }); + break; + } + } + + QueryInterface = ChromeUtils.generateQI([Ci.nsISupportsWeakReference]); +} + /** * All the privileged actions. */ @@ -187,6 +229,7 @@ class ChromeActions { this.contentDispositionFilename = contentDispositionFilename; this.sandbox = null; this.unloadListener = null; + this.observer = new PrefObserver(domWindow); } createSandbox(data, sendResponse) { @@ -300,7 +343,7 @@ class ChromeActions { Services.prefs.getIntPref("mousewheel.with_meta.action") === 3, supportsPinchToZoom: Services.prefs.getBoolPref("apz.allow_zooming"), supportsCaretBrowsingMode: Services.prefs.getBoolPref( - "accessibility.browsewithcaret" + caretBrowsingModePref ), }; } @@ -330,9 +373,8 @@ class ChromeActions { } reportTelemetry(data) { - const probeInfo = JSON.parse(data); const actor = getActor(this.domWindow); - actor?.sendAsyncMessage("PDFJS:Parent:reportTelemetry", probeInfo); + actor?.sendAsyncMessage("PDFJS:Parent:reportTelemetry", data); } updateFindControlState(data) { @@ -434,6 +476,7 @@ class ChromeActions { hasSomethingToUndo: false, hasSomethingToRedo: false, hasSelectedEditor: false, + hasSelectedText: false, }; } const { editorStates } = doc; diff --git a/toolkit/components/pdfjs/content/PdfjsChild.sys.mjs b/toolkit/components/pdfjs/content/PdfjsChild.sys.mjs index 0b5276ec12..b4162966dd 100644 --- a/toolkit/components/pdfjs/content/PdfjsChild.sys.mjs +++ b/toolkit/components/pdfjs/content/PdfjsChild.sys.mjs @@ -14,30 +14,27 @@ */ export class PdfjsChild extends JSWindowActorChild { - init(supportsFind) { - if (supportsFind) { + init(aSupportsFind) { + if (aSupportsFind) { this.sendAsyncMessage("PDFJS:Parent:addEventListener"); } } - dispatchEvent(type, detail) { + dispatchEvent(aType, aDetail) { + aDetail &&= Cu.cloneInto(aDetail, this.contentWindow); const contentWindow = this.contentWindow; const forward = contentWindow.document.createEvent("CustomEvent"); - forward.initCustomEvent(type, true, true, detail); + forward.initCustomEvent(aType, true, true, aDetail); contentWindow.dispatchEvent(forward); } receiveMessage(msg) { switch (msg.name) { - case "PDFJS:Child:handleEvent": { - let detail = Cu.cloneInto(msg.data.detail, this.contentWindow); - this.dispatchEvent(msg.data.type, detail); + case "PDFJS:Child:handleEvent": + this.dispatchEvent(msg.data.type, msg.data.detail); break; - } - case "PDFJS:Editing": - let data = Cu.cloneInto(msg.data, this.contentWindow); - this.dispatchEvent("editingaction", data); + this.dispatchEvent("editingaction", msg.data); break; case "PDFJS:ZoomIn": case "PDFJS:ZoomOut": diff --git a/toolkit/components/pdfjs/content/build/pdf.mjs b/toolkit/components/pdfjs/content/build/pdf.mjs index 72dcbae165..4e51efdb78 100644 --- a/toolkit/components/pdfjs/content/build/pdf.mjs +++ b/toolkit/components/pdfjs/content/build/pdf.mjs @@ -139,7 +139,8 @@ const AnnotationEditorParamsType = { HIGHLIGHT_COLOR: 31, HIGHLIGHT_DEFAULT_COLOR: 32, HIGHLIGHT_THICKNESS: 33, - HIGHLIGHT_FREE: 34 + HIGHLIGHT_FREE: 34, + HIGHLIGHT_SHOW_ALL: 35 }; const PermissionFlag = { PRINT: 0x04, @@ -1591,9 +1592,173 @@ function setLayerDimensions(div, viewport, mustFlip = false, mustRotate = true) } } +;// CONCATENATED MODULE: ./src/display/editor/toolbar.js + +class EditorToolbar { + #toolbar = null; + #colorPicker = null; + #editor; + #buttons = null; + constructor(editor) { + this.#editor = editor; + } + render() { + const editToolbar = this.#toolbar = document.createElement("div"); + editToolbar.className = "editToolbar"; + editToolbar.setAttribute("role", "toolbar"); + editToolbar.addEventListener("contextmenu", noContextMenu); + editToolbar.addEventListener("pointerdown", EditorToolbar.#pointerDown); + const buttons = this.#buttons = document.createElement("div"); + buttons.className = "buttons"; + editToolbar.append(buttons); + const position = this.#editor.toolbarPosition; + if (position) { + const { + style + } = editToolbar; + const x = this.#editor._uiManager.direction === "ltr" ? 1 - position[0] : position[0]; + style.insetInlineEnd = `${100 * x}%`; + style.top = `calc(${100 * position[1]}% + var(--editor-toolbar-vert-offset))`; + } + this.#addDeleteButton(); + return editToolbar; + } + static #pointerDown(e) { + e.stopPropagation(); + } + #focusIn(e) { + this.#editor._focusEventsAllowed = false; + e.preventDefault(); + e.stopPropagation(); + } + #focusOut(e) { + this.#editor._focusEventsAllowed = true; + e.preventDefault(); + e.stopPropagation(); + } + #addListenersToElement(element) { + element.addEventListener("focusin", this.#focusIn.bind(this), { + capture: true + }); + element.addEventListener("focusout", this.#focusOut.bind(this), { + capture: true + }); + element.addEventListener("contextmenu", noContextMenu); + } + hide() { + this.#toolbar.classList.add("hidden"); + this.#colorPicker?.hideDropdown(); + } + show() { + this.#toolbar.classList.remove("hidden"); + } + #addDeleteButton() { + const button = document.createElement("button"); + button.className = "delete"; + button.tabIndex = 0; + button.setAttribute("data-l10n-id", `pdfjs-editor-remove-${this.#editor.editorType}-button`); + this.#addListenersToElement(button); + button.addEventListener("click", e => { + this.#editor._uiManager.delete(); + }); + this.#buttons.append(button); + } + get #divider() { + const divider = document.createElement("div"); + divider.className = "divider"; + return divider; + } + addAltTextButton(button) { + this.#addListenersToElement(button); + this.#buttons.prepend(button, this.#divider); + } + addColorPicker(colorPicker) { + this.#colorPicker = colorPicker; + const button = colorPicker.renderButton(); + this.#addListenersToElement(button); + this.#buttons.prepend(button, this.#divider); + } + remove() { + this.#toolbar.remove(); + this.#colorPicker?.destroy(); + this.#colorPicker = null; + } +} +class HighlightToolbar { + #buttons = null; + #toolbar = null; + #uiManager; + constructor(uiManager) { + this.#uiManager = uiManager; + } + #render() { + const editToolbar = this.#toolbar = document.createElement("div"); + editToolbar.className = "editToolbar"; + editToolbar.setAttribute("role", "toolbar"); + editToolbar.addEventListener("contextmenu", noContextMenu); + const buttons = this.#buttons = document.createElement("div"); + buttons.className = "buttons"; + editToolbar.append(buttons); + this.#addHighlightButton(); + return editToolbar; + } + #getLastPoint(boxes, isLTR) { + let lastY = 0; + let lastX = 0; + for (const box of boxes) { + const y = box.y + box.height; + if (y < lastY) { + continue; + } + const x = box.x + (isLTR ? box.width : 0); + if (y > lastY) { + lastX = x; + lastY = y; + continue; + } + if (isLTR) { + if (x > lastX) { + lastX = x; + } + } else if (x < lastX) { + lastX = x; + } + } + return [isLTR ? 1 - lastX : lastX, lastY]; + } + show(parent, boxes, isLTR) { + const [x, y] = this.#getLastPoint(boxes, isLTR); + const { + style + } = this.#toolbar ||= this.#render(); + parent.append(this.#toolbar); + style.insetInlineEnd = `${100 * x}%`; + style.top = `calc(${100 * y}% + var(--editor-toolbar-vert-offset))`; + } + hide() { + this.#toolbar.remove(); + } + #addHighlightButton() { + const button = document.createElement("button"); + button.className = "highlightButton"; + button.tabIndex = 0; + button.setAttribute("data-l10n-id", `pdfjs-highlight-floating-button`); + const span = document.createElement("span"); + button.append(span); + span.className = "visuallyHidden"; + span.setAttribute("data-l10n-id", "pdfjs-editor-highlight-button-label"); + button.addEventListener("contextmenu", noContextMenu); + button.addEventListener("click", () => { + this.#uiManager.highlightSelection("floating_button"); + }); + this.#buttons.append(button); + } +} + ;// CONCATENATED MODULE: ./src/display/editor/tools.js + function bindEvents(obj, element, names) { for (const name of names) { element.addEventListener(name, obj[name].bind(obj)); @@ -1933,10 +2098,12 @@ class AnnotationEditorUIManager { #draggingEditors = null; #editorTypes = null; #editorsToRescale = new Set(); + #enableHighlightFloatingButton = false; #filterFactory = null; #focusMainContainerTimeoutId = null; #highlightColors = null; #highlightWhenShiftUp = false; + #highlightToolbar = null; #idManager = new IdManager(); #isEnabled = false; #isWaiting = false; @@ -1947,6 +2114,7 @@ class AnnotationEditorUIManager { #selectedEditors = new Set(); #selectedTextNode = null; #pageColors = null; + #showAllStates = null; #boundBlur = this.blur.bind(this); #boundFocus = this.focus.bind(this); #boundCopy = this.copy.bind(this); @@ -2031,7 +2199,7 @@ class AnnotationEditorUIManager { checker: arrowChecker }]])); } - constructor(container, viewer, altTextManager, eventBus, pdfDocument, pageColors, highlightColors, mlManager) { + constructor(container, viewer, altTextManager, eventBus, pdfDocument, pageColors, highlightColors, enableHighlightFloatingButton, mlManager) { this.#container = container; this.#viewer = viewer; this.#altTextManager = altTextManager; @@ -2041,10 +2209,12 @@ class AnnotationEditorUIManager { this._eventBus._on("scalechanging", this.#boundOnScaleChanging); this._eventBus._on("rotationchanging", this.#boundOnRotationChanging); this.#addSelectionListener(); + this.#addKeyboardManager(); this.#annotationStorage = pdfDocument.annotationStorage; this.#filterFactory = pdfDocument.filterFactory; this.#pageColors = pageColors; this.#highlightColors = highlightColors || null; + this.#enableHighlightFloatingButton = enableHighlightFloatingButton; this.#mlManager = mlManager || null; this.viewParameters = { realScale: PixelsPerInch.PDF_TO_CSS_UNITS, @@ -2069,6 +2239,8 @@ class AnnotationEditorUIManager { this.#selectedEditors.clear(); this.#commandManager.destroy(); this.#altTextManager?.destroy(); + this.#highlightToolbar?.hide(); + this.#highlightToolbar = null; if (this.#focusMainContainerTimeoutId) { clearTimeout(this.#focusMainContainerTimeoutId); this.#focusMainContainerTimeoutId = null; @@ -2149,6 +2321,11 @@ class AnnotationEditorUIManager { this.commitOrRemove(); this.viewParameters.rotation = pagesRotation; } + #getAnchorElementForSelection({ + anchorNode + }) { + return anchorNode.nodeType === Node.TEXT_NODE ? anchorNode.parentElement : anchorNode; + } highlightSelection(methodOfCreation = "") { const selection = document.getSelection(); if (!selection || selection.isCollapsed) { @@ -2160,15 +2337,20 @@ class AnnotationEditorUIManager { focusNode, focusOffset } = selection; - const anchorElement = anchorNode.nodeType === Node.TEXT_NODE ? anchorNode.parentElement : anchorNode; + const text = selection.toString(); + const anchorElement = this.#getAnchorElementForSelection(selection); const textLayer = anchorElement.closest(".textLayer"); const boxes = this.getSelectionBoxes(textLayer); + if (!boxes) { + return; + } selection.empty(); if (this.#mode === AnnotationEditorType.NONE) { this._eventBus.dispatch("showannotationeditorui", { source: this, mode: AnnotationEditorType.HIGHLIGHT }); + this.showAllEditors("highlight", true, true); } for (const layer of this.#allLayers.values()) { if (layer.hasTextLayer(textLayer)) { @@ -2181,12 +2363,27 @@ class AnnotationEditorUIManager { anchorNode, anchorOffset, focusNode, - focusOffset + focusOffset, + text }); break; } } } + #displayHighlightToolbar() { + const selection = document.getSelection(); + if (!selection || selection.isCollapsed) { + return; + } + const anchorElement = this.#getAnchorElementForSelection(selection); + const textLayer = anchorElement.closest(".textLayer"); + const boxes = this.getSelectionBoxes(textLayer); + if (!boxes) { + return; + } + this.#highlightToolbar ||= new HighlightToolbar(this); + this.#highlightToolbar.show(textLayer, boxes, this.direction === "ltr"); + } addToAnnotationStorage(editor) { if (!editor.isEmpty() && this.#annotationStorage && !this.#annotationStorage.has(editor.id)) { this.#annotationStorage.setValue(editor.id, editor); @@ -2196,6 +2393,7 @@ class AnnotationEditorUIManager { const selection = document.getSelection(); if (!selection || selection.isCollapsed) { if (this.#selectedTextNode) { + this.#highlightToolbar?.hide(); this.#selectedTextNode = null; this.#dispatchUpdateStates({ hasSelectedText: false @@ -2209,9 +2407,11 @@ class AnnotationEditorUIManager { if (anchorNode === this.#selectedTextNode) { return; } - const anchorElement = anchorNode.nodeType === Node.TEXT_NODE ? anchorNode.parentElement : anchorNode; - if (!anchorElement.closest(".textLayer")) { + const anchorElement = this.#getAnchorElementForSelection(selection); + const textLayer = anchorElement.closest(".textLayer"); + if (!textLayer) { if (this.#selectedTextNode) { + this.#highlightToolbar?.hide(); this.#selectedTextNode = null; this.#dispatchUpdateStates({ hasSelectedText: false @@ -2219,13 +2419,17 @@ class AnnotationEditorUIManager { } return; } + this.#highlightToolbar?.hide(); this.#selectedTextNode = anchorNode; this.#dispatchUpdateStates({ hasSelectedText: true }); - if (this.#mode !== AnnotationEditorType.HIGHLIGHT) { + if (this.#mode !== AnnotationEditorType.HIGHLIGHT && this.#mode !== AnnotationEditorType.NONE) { return; } + if (this.#mode === AnnotationEditorType.HIGHLIGHT) { + this.showAllEditors("highlight", true, true); + } this.#highlightWhenShiftUp = this.isShiftKeyDown; if (!this.isShiftKeyDown) { const pointerup = e => { @@ -2235,13 +2439,20 @@ class AnnotationEditorUIManager { window.removeEventListener("pointerup", pointerup); window.removeEventListener("blur", pointerup); if (e.type === "pointerup") { - this.highlightSelection("main_toolbar"); + this.#onSelectEnd("main_toolbar"); } }; window.addEventListener("pointerup", pointerup); window.addEventListener("blur", pointerup); } } + #onSelectEnd(methodOfCreation = "") { + if (this.#mode === AnnotationEditorType.HIGHLIGHT) { + this.highlightSelection(methodOfCreation); + } else if (this.#enableHighlightFloatingButton) { + this.#displayHighlightToolbar(); + } + } #addSelectionListener() { document.addEventListener("selectionchange", this.#boundSelectionChange); } @@ -2260,7 +2471,7 @@ class AnnotationEditorUIManager { this.isShiftKeyDown = false; if (this.#highlightWhenShiftUp) { this.#highlightWhenShiftUp = false; - this.highlightSelection("main_toolbar"); + this.#onSelectEnd("main_toolbar"); } if (!this.hasSelection) { return; @@ -2398,7 +2609,7 @@ class AnnotationEditorUIManager { if (!this.isShiftKeyDown && event.key === "Shift") { this.isShiftKeyDown = true; } - if (!this.isEditorHandlingKeyboard) { + if (this.#mode !== AnnotationEditorType.NONE && !this.isEditorHandlingKeyboard) { AnnotationEditorUIManager._keyboardManager.exec(this, event); } } @@ -2407,7 +2618,7 @@ class AnnotationEditorUIManager { this.isShiftKeyDown = false; if (this.#highlightWhenShiftUp) { this.#highlightWhenShiftUp = false; - this.highlightSelection("main_toolbar"); + this.#onSelectEnd("main_toolbar"); } } } @@ -2447,7 +2658,6 @@ class AnnotationEditorUIManager { setEditingState(isEditing) { if (isEditing) { this.#addFocusManager(); - this.#addKeyboardManager(); this.#addCopyPasteListeners(); this.#dispatchUpdateStates({ isEditing: this.#mode !== AnnotationEditorType.NONE, @@ -2458,7 +2668,6 @@ class AnnotationEditorUIManager { }); } else { this.#removeFocusManager(); - this.#removeKeyboardManager(); this.#removeCopyPasteListeners(); this.#dispatchUpdateStates({ isEditing: false @@ -2554,6 +2763,20 @@ class AnnotationEditorUIManager { case AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR: this.#mainHighlightColorPicker?.updateColor(value); break; + case AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL: + this._eventBus.dispatch("reporttelemetry", { + source: this, + details: { + type: "editing", + data: { + type: "highlight", + action: "toggle_visibility" + } + } + }); + (this.#showAllStates ||= new Map()).set(type, value); + this.showAllEditors("highlight", value); + break; } for (const editor of this.#selectedEditors) { editor.updateParams(type, value); @@ -2562,6 +2785,17 @@ class AnnotationEditorUIManager { editorType.updateDefaultParams(type, value); } } + showAllEditors(type, visible, updateButton = false) { + for (const editor of this.#allEditors.values()) { + if (editor.editorType === type) { + editor.show(visible); + } + } + const state = this.#showAllStates?.get(AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL) ?? true; + if (state !== visible) { + this.#dispatchUpdateUI([[AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL, visible]]); + } + } enableWaiting(mustWait = false) { if (this.#isWaiting === mustWait) { return; @@ -2582,6 +2816,9 @@ class AnnotationEditorUIManager { for (const layer of this.#allLayers.values()) { layer.enable(); } + for (const editor of this.#allEditors.values()) { + editor.enable(); + } } } #disableAll() { @@ -2591,6 +2828,9 @@ class AnnotationEditorUIManager { for (const layer of this.#allLayers.values()) { layer.disable(); } + for (const editor of this.#allEditors.values()) { + editor.disable(); + } } } getEditors(pageIndex) { @@ -2641,6 +2881,7 @@ class AnnotationEditorUIManager { layer.addOrRebuild(editor); } else { this.addEditor(editor); + this.addToAnnotationStorage(editor); } } setActiveEditor(editor) { @@ -3172,98 +3413,6 @@ class AltText { } } -;// CONCATENATED MODULE: ./src/display/editor/toolbar.js - -class EditorToolbar { - #toolbar = null; - #colorPicker = null; - #editor; - #buttons = null; - constructor(editor) { - this.#editor = editor; - } - render() { - const editToolbar = this.#toolbar = document.createElement("div"); - editToolbar.className = "editToolbar"; - editToolbar.addEventListener("contextmenu", noContextMenu); - editToolbar.addEventListener("pointerdown", EditorToolbar.#pointerDown); - const buttons = this.#buttons = document.createElement("div"); - buttons.className = "buttons"; - editToolbar.append(buttons); - const position = this.#editor.toolbarPosition; - if (position) { - const { - style - } = editToolbar; - const x = this.#editor._uiManager.direction === "ltr" ? 1 - position[0] : position[0]; - style.insetInlineEnd = `${100 * x}%`; - style.top = `calc(${100 * position[1]}% + var(--editor-toolbar-vert-offset))`; - } - this.#addDeleteButton(); - return editToolbar; - } - static #pointerDown(e) { - e.stopPropagation(); - } - #focusIn(e) { - this.#editor._focusEventsAllowed = false; - e.preventDefault(); - e.stopPropagation(); - } - #focusOut(e) { - this.#editor._focusEventsAllowed = true; - e.preventDefault(); - e.stopPropagation(); - } - #addListenersToElement(element) { - element.addEventListener("focusin", this.#focusIn.bind(this), { - capture: true - }); - element.addEventListener("focusout", this.#focusOut.bind(this), { - capture: true - }); - element.addEventListener("contextmenu", noContextMenu); - } - hide() { - this.#toolbar.classList.add("hidden"); - this.#colorPicker?.hideDropdown(); - } - show() { - this.#toolbar.classList.remove("hidden"); - } - #addDeleteButton() { - const button = document.createElement("button"); - button.className = "delete"; - button.tabIndex = 0; - button.setAttribute("data-l10n-id", `pdfjs-editor-remove-${this.#editor.editorType}-button`); - this.#addListenersToElement(button); - button.addEventListener("click", e => { - this.#editor._uiManager.delete(); - }); - this.#buttons.append(button); - } - get #divider() { - const divider = document.createElement("div"); - divider.className = "divider"; - return divider; - } - addAltTextButton(button) { - this.#addListenersToElement(button); - this.#buttons.prepend(button, this.#divider); - } - addColorPicker(colorPicker) { - this.#colorPicker = colorPicker; - const button = colorPicker.renderButton(); - this.#addListenersToElement(button); - this.#buttons.prepend(button, this.#divider); - } - remove() { - this.#toolbar.remove(); - this.#colorPicker?.destroy(); - this.#colorPicker = null; - } -} - ;// CONCATENATED MODULE: ./src/display/editor/editor.js @@ -3273,6 +3422,7 @@ class EditorToolbar { class AnnotationEditor { #allResizerDivs = null; #altText = null; + #disabled = false; #keepAspectRatio = false; #resizersDiv = null; #savedDimensions = null; @@ -3289,6 +3439,7 @@ class AnnotationEditor { #prevDragY = 0; #telemetryTimeouts = null; _initialOptions = Object.create(null); + _isVisible = true; _uiManager = null; _focusEventsAllowed = true; _l10nPromise = null; @@ -3555,6 +3706,9 @@ class AnnotationEditor { return [-x, -y]; } } + get _mustFixPosition() { + return true; + } fixAndSetPosition(rotation = this.rotation) { const [pageWidth, pageHeight] = this.pageDimensions; let { @@ -3567,23 +3721,25 @@ class AnnotationEditor { height *= pageHeight; x *= pageWidth; y *= pageHeight; - switch (rotation) { - case 0: - x = Math.max(0, Math.min(pageWidth - width, x)); - y = Math.max(0, Math.min(pageHeight - height, y)); - break; - case 90: - x = Math.max(0, Math.min(pageWidth - height, x)); - y = Math.min(pageHeight, Math.max(width, y)); - break; - case 180: - x = Math.min(pageWidth, Math.max(width, x)); - y = Math.min(pageHeight, Math.max(height, y)); - break; - case 270: - x = Math.min(pageWidth, Math.max(height, x)); - y = Math.max(0, Math.min(pageHeight - width, y)); - break; + if (this._mustFixPosition) { + switch (rotation) { + case 0: + x = Math.max(0, Math.min(pageWidth - width, x)); + y = Math.max(0, Math.min(pageHeight - height, y)); + break; + case 90: + x = Math.max(0, Math.min(pageWidth - height, x)); + y = Math.min(pageHeight, Math.max(width, y)); + break; + case 180: + x = Math.min(pageWidth, Math.max(width, x)); + y = Math.min(pageHeight, Math.max(height, y)); + break; + case 270: + x = Math.min(pageWidth, Math.max(height, x)); + y = Math.max(0, Math.min(pageHeight - width, y)); + break; + } } this.x = x /= pageWidth; this.y = y /= pageHeight; @@ -3902,7 +4058,10 @@ class AnnotationEditor { this.div.setAttribute("data-editor-rotation", (360 - this.rotation) % 360); this.div.className = this.name; this.div.setAttribute("id", this.id); - this.div.setAttribute("tabIndex", 0); + this.div.tabIndex = this.#disabled ? -1 : 0; + if (!this._isVisible) { + this.div.classList.add("hidden"); + } this.setInForeground(); this.div.addEventListener("focusin", this.#boundFocusin); this.div.addEventListener("focusout", this.#boundFocusout); @@ -4100,6 +4259,7 @@ class AnnotationEditor { } this.#telemetryTimeouts = null; } + this.parent = null; } get isResizable() { return false; @@ -4319,6 +4479,22 @@ class AnnotationEditor { } }); } + show(visible = this._isVisible) { + this.div.classList.toggle("hidden", !visible); + this._isVisible = visible; + } + enable() { + if (this.div) { + this.div.tabIndex = 0; + } + this.#disabled = false; + } + disable() { + if (this.div) { + this.div.tabIndex = -1; + } + this.#disabled = true; + } } class FakeEditor extends AnnotationEditor { constructor(params) { @@ -8373,18 +8549,44 @@ class Metadata { const INTERNAL = Symbol("INTERNAL"); class OptionalContentGroup { + #isDisplay = false; + #isPrint = false; + #userSet = false; #visible = true; - constructor(name, intent) { + constructor(renderingIntent, { + name, + intent, + usage + }) { + this.#isDisplay = !!(renderingIntent & RenderingIntentFlag.DISPLAY); + this.#isPrint = !!(renderingIntent & RenderingIntentFlag.PRINT); this.name = name; this.intent = intent; + this.usage = usage; } get visible() { - return this.#visible; + if (this.#userSet) { + return this.#visible; + } + if (!this.#visible) { + return false; + } + const { + print, + view + } = this.usage; + if (this.#isDisplay) { + return view?.viewState !== "OFF"; + } else if (this.#isPrint) { + return print?.printState !== "OFF"; + } + return true; } - _setVisible(internal, visible) { + _setVisible(internal, visible, userSet = false) { if (internal !== INTERNAL) { unreachable("Internal method `_setVisible` called."); } + this.#userSet = userSet; this.#visible = visible; } } @@ -8393,7 +8595,8 @@ class OptionalContentConfig { #groups = new Map(); #initialHash = null; #order = null; - constructor(data) { + constructor(data, renderingIntent = RenderingIntentFlag.DISPLAY) { + this.renderingIntent = renderingIntent; this.name = null; this.creator = null; if (data === null) { @@ -8403,7 +8606,7 @@ class OptionalContentConfig { this.creator = data.creator; this.#order = data.order; for (const group of data.groups) { - this.#groups.set(group.id, new OptionalContentGroup(group.name, group.intent)); + this.#groups.set(group.id, new OptionalContentGroup(renderingIntent, group)); } if (data.baseState === "OFF") { for (const group of this.#groups.values()) { @@ -8524,11 +8727,43 @@ class OptionalContentConfig { return true; } setVisibility(id, visible = true) { - if (!this.#groups.has(id)) { + const group = this.#groups.get(id); + if (!group) { warn(`Optional content group not found: ${id}`); return; } - this.#groups.get(id)._setVisible(INTERNAL, !!visible); + group._setVisible(INTERNAL, !!visible, true); + this.#cachedGetHash = null; + } + setOCGState({ + state, + preserveRB + }) { + let operator; + for (const elem of state) { + switch (elem) { + case "ON": + case "OFF": + case "Toggle": + operator = elem; + continue; + } + const group = this.#groups.get(elem); + if (!group) { + continue; + } + switch (operator) { + case "ON": + group._setVisible(INTERNAL, true); + break; + case "OFF": + group._setVisible(INTERNAL, false); + break; + case "Toggle": + group._setVisible(INTERNAL, !group.visible); + break; + } + } this.#cachedGetHash = null; } get hasInitialVisibility() { @@ -8970,7 +9205,7 @@ function getDocument(src) { } const fetchDocParams = { docId, - apiVersion: "4.1.249", + apiVersion: "4.1.342", data, password, disableAutoFetch, @@ -9207,8 +9442,13 @@ class PDFDocumentProxy { getOutline() { return this._transport.getOutline(); } - getOptionalContentConfig() { - return this._transport.getOptionalContentConfig(); + getOptionalContentConfig({ + intent = "display" + } = {}) { + const { + renderingIntent + } = this._transport.getRenderingIntent(intent); + return this._transport.getOptionalContentConfig(renderingIntent); } getPermissions() { return this._transport.getPermissions(); @@ -9299,8 +9539,10 @@ class PDFPageProxy { getAnnotations({ intent = "display" } = {}) { - const intentArgs = this._transport.getRenderingIntent(intent); - return this._transport.getAnnotations(this._pageIndex, intentArgs.renderingIntent); + const { + renderingIntent + } = this._transport.getRenderingIntent(intent); + return this._transport.getAnnotations(this._pageIndex, renderingIntent); } getJSActions() { return this._transport.getPageJSActions(this._pageIndex); @@ -9328,21 +9570,23 @@ class PDFPageProxy { }) { this._stats?.time("Overall"); const intentArgs = this._transport.getRenderingIntent(intent, annotationMode, printAnnotationStorage); + const { + renderingIntent, + cacheKey + } = intentArgs; this.#pendingCleanup = false; this.#abortDelayedCleanup(); - if (!optionalContentConfigPromise) { - optionalContentConfigPromise = this._transport.getOptionalContentConfig(); - } - let intentState = this._intentStates.get(intentArgs.cacheKey); + optionalContentConfigPromise ||= this._transport.getOptionalContentConfig(renderingIntent); + let intentState = this._intentStates.get(cacheKey); if (!intentState) { intentState = Object.create(null); - this._intentStates.set(intentArgs.cacheKey, intentState); + this._intentStates.set(cacheKey, intentState); } if (intentState.streamReaderCancelTimeout) { clearTimeout(intentState.streamReaderCancelTimeout); intentState.streamReaderCancelTimeout = null; } - const intentPrint = !!(intentArgs.renderingIntent & RenderingIntentFlag.PRINT); + const intentPrint = !!(renderingIntent & RenderingIntentFlag.PRINT); if (!intentState.displayReadyCapability) { intentState.displayReadyCapability = new PromiseCapability(); intentState.operatorList = { @@ -9399,6 +9643,9 @@ class PDFPageProxy { return; } this._stats?.time("Rendering"); + if (!(optionalContentConfig.renderingIntent & renderingIntent)) { + throw new Error("Must use the same `intent`-argument when calling the `PDFPageProxy.render` " + "and `PDFDocumentProxy.getOptionalContentConfig` methods."); + } internalRenderTask.initializeGraphics({ transparency, optionalContentConfig @@ -10338,8 +10585,8 @@ class WorkerTransport { getOutline() { return this.messageHandler.sendWithPromise("GetOutline", null); } - getOptionalContentConfig() { - return this.messageHandler.sendWithPromise("GetOptionalContentConfig", null).then(results => new OptionalContentConfig(results)); + getOptionalContentConfig(renderingIntent) { + return this.#cacheSimpleMethod("GetOptionalContentConfig").then(data => new OptionalContentConfig(data, renderingIntent)); } getPermissions() { return this.messageHandler.sendWithPromise("GetPermissions", null); @@ -10602,8 +10849,8 @@ class InternalRenderTask { } } } -const version = "4.1.249"; -const build = "d07f37f44"; +const version = "4.1.342"; +const build = "e384df6f1"; ;// CONCATENATED MODULE: ./src/shared/scripting_utils.js function makeColorComp(n) { @@ -11002,9 +11249,12 @@ class AnnotationElement { container.tabIndex = DEFAULT_TAB_INDEX; } container.style.zIndex = this.parent.zIndex++; - if (this.data.popupRef) { + if (data.popupRef) { container.setAttribute("aria-haspopup", "dialog"); } + if (data.alternativeText) { + container.title = data.alternativeText; + } if (data.noRotate) { container.classList.add("norotate"); } @@ -11652,9 +11902,6 @@ class TextAnnotationElement extends AnnotationElement { } class WidgetAnnotationElement extends AnnotationElement { render() { - if (this.data.alternativeText) { - this.container.title = this.data.alternativeText; - } return this.container; } showElementAndHideCanvas(element) { @@ -12255,9 +12502,6 @@ class PushButtonWidgetAnnotationElement extends LinkAnnotationElement { render() { const container = super.render(); container.classList.add("buttonWidgetAnnotation", "pushButton"); - if (this.data.alternativeText) { - container.title = this.data.alternativeText; - } const linkElement = container.lastChild; if (this.enableScripting && this.hasJSActions && linkElement) { this._setDefaultPropertiesFromJS(linkElement); @@ -13271,11 +13515,13 @@ class AnnotationLayer { +const EOL_PATTERN = /\r\n?|\n/g; class FreeTextEditor extends AnnotationEditor { #boundEditorDivBlur = this.editorDivBlur.bind(this); #boundEditorDivFocus = this.editorDivFocus.bind(this); #boundEditorDivInput = this.editorDivInput.bind(this); #boundEditorDivKeydown = this.editorDivKeydown.bind(this); + #boundEditorDivPaste = this.editorDivPaste.bind(this); #color; #content = ""; #editorDivId = `${this.id}-editor`; @@ -13428,6 +13674,7 @@ class FreeTextEditor extends AnnotationEditor { this.editorDiv.addEventListener("focus", this.#boundEditorDivFocus); this.editorDiv.addEventListener("blur", this.#boundEditorDivBlur); this.editorDiv.addEventListener("input", this.#boundEditorDivInput); + this.editorDiv.addEventListener("paste", this.#boundEditorDivPaste); } disableEditMode() { if (!this.isInEditMode()) { @@ -13443,6 +13690,7 @@ class FreeTextEditor extends AnnotationEditor { this.editorDiv.removeEventListener("focus", this.#boundEditorDivFocus); this.editorDiv.removeEventListener("blur", this.#boundEditorDivBlur); this.editorDiv.removeEventListener("input", this.#boundEditorDivInput); + this.editorDiv.removeEventListener("paste", this.#boundEditorDivPaste); this.div.focus({ preventScroll: true }); @@ -13484,10 +13732,8 @@ class FreeTextEditor extends AnnotationEditor { #extractText() { const buffer = []; this.editorDiv.normalize(); - const EOL_PATTERN = /\r\n?|\n/g; for (const child of this.editorDiv.childNodes) { - const content = child.nodeType === Node.TEXT_NODE ? child.nodeValue : child.innerText; - buffer.push(content.replaceAll(EOL_PATTERN, "")); + buffer.push(FreeTextEditor.#getNodeContent(child)); } return buffer.join("\n"); } @@ -13657,6 +13903,85 @@ class FreeTextEditor extends AnnotationEditor { } return this.div; } + static #getNodeContent(node) { + return (node.nodeType === Node.TEXT_NODE ? node.nodeValue : node.innerText).replaceAll(EOL_PATTERN, ""); + } + editorDivPaste(event) { + const clipboardData = event.clipboardData || window.clipboardData; + const { + types + } = clipboardData; + if (types.length === 1 && types[0] === "text/plain") { + return; + } + event.preventDefault(); + const paste = FreeTextEditor.#deserializeContent(clipboardData.getData("text") || "").replaceAll(EOL_PATTERN, "\n"); + if (!paste) { + return; + } + const selection = window.getSelection(); + if (!selection.rangeCount) { + return; + } + this.editorDiv.normalize(); + selection.deleteFromDocument(); + const range = selection.getRangeAt(0); + if (!paste.includes("\n")) { + range.insertNode(document.createTextNode(paste)); + this.editorDiv.normalize(); + selection.collapseToStart(); + return; + } + const { + startContainer, + startOffset + } = range; + const bufferBefore = []; + const bufferAfter = []; + if (startContainer.nodeType === Node.TEXT_NODE) { + const parent = startContainer.parentElement; + bufferAfter.push(startContainer.nodeValue.slice(startOffset).replaceAll(EOL_PATTERN, "")); + if (parent !== this.editorDiv) { + let buffer = bufferBefore; + for (const child of this.editorDiv.childNodes) { + if (child === parent) { + buffer = bufferAfter; + continue; + } + buffer.push(FreeTextEditor.#getNodeContent(child)); + } + } + bufferBefore.push(startContainer.nodeValue.slice(0, startOffset).replaceAll(EOL_PATTERN, "")); + } else if (startContainer === this.editorDiv) { + let buffer = bufferBefore; + let i = 0; + for (const child of this.editorDiv.childNodes) { + if (i++ === startOffset) { + buffer = bufferAfter; + } + buffer.push(FreeTextEditor.#getNodeContent(child)); + } + } + this.#content = `${bufferBefore.join("\n")}${paste}${bufferAfter.join("\n")}`; + this.#setContent(); + const newRange = new Range(); + let beforeLength = bufferBefore.reduce((acc, line) => acc + line.length, 0); + for (const { + firstChild + } of this.editorDiv.childNodes) { + if (firstChild.nodeType === Node.TEXT_NODE) { + const length = firstChild.nodeValue.length; + if (beforeLength <= length) { + newRange.setStart(firstChild, beforeLength); + newRange.setEnd(firstChild, beforeLength); + break; + } + beforeLength -= length; + } + } + selection.removeAllRanges(); + selection.addRange(newRange); + } #setContent() { this.editorDiv.replaceChildren(); if (!this.#content) { @@ -14392,6 +14717,7 @@ class ColorPicker { #dropdown = null; #dropdownWasFromKeyboard = false; #isMainColorPicker = false; + #editor = null; #eventBus; #uiManager = null; #type; @@ -14405,6 +14731,7 @@ class ColorPicker { if (editor) { this.#isMainColorPicker = false; this.#type = AnnotationEditorParamsType.HIGHLIGHT_COLOR; + this.#editor = editor; } else { this.#isMainColorPicker = true; this.#type = AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR; @@ -14423,6 +14750,7 @@ class ColorPicker { button.addEventListener("keydown", this.#boundKeyDown); const swatch = this.#buttonSwatch = document.createElement("span"); swatch.className = "swatch"; + swatch.setAttribute("aria-hidden", true); swatch.style.backgroundColor = this.#defaultColor; button.append(swatch); return button; @@ -14546,7 +14874,11 @@ class ColorPicker { return this.#dropdown && !this.#dropdown.classList.contains("hidden"); } _hideDropdownFromKeyboard() { - if (this.#isMainColorPicker || !this.#isDropdownVisible) { + if (this.#isMainColorPicker) { + return; + } + if (!this.#isDropdownVisible) { + this.#editor?.unselect(); return; } this.hideDropdown(); @@ -14600,6 +14932,7 @@ class HighlightEditor extends AnnotationEditor { #lastPoint = null; #opacity; #outlineId = null; + #text = ""; #thickness; #methodOfCreation = ""; static _defaultColor = null; @@ -14633,6 +14966,7 @@ class HighlightEditor extends AnnotationEditor { this.#opacity = params.opacity || HighlightEditor._defaultOpacity; this.#boxes = params.boxes || null; this.#methodOfCreation = params.methodOfCreation || ""; + this.#text = params.text || ""; this._isDraggable = false; if (params.highlightId > -1) { this.#isFreeHighlight = true; @@ -14857,11 +15191,11 @@ class HighlightEditor extends AnnotationEditor { this.div.focus(); } remove() { - super.remove(); this.#cleanDrawLayer(); this._reportTelemetry({ action: "deleted" }); + super.remove(); } rebuild() { if (!this.parent) { @@ -14885,6 +15219,7 @@ class HighlightEditor extends AnnotationEditor { mustBeSelected = !this.parent && this.div?.classList.contains("selectedEditor"); } super.setParent(parent); + this.show(this._isVisible); if (mustBeSelected) { this.select(); } @@ -14979,6 +15314,12 @@ class HighlightEditor extends AnnotationEditor { return this.div; } const div = super.render(); + if (this.#text) { + const mark = document.createElement("mark"); + div.append(mark); + mark.append(document.createTextNode(this.#text)); + mark.className = "visuallyHidden"; + } if (this.#isFreeHighlight) { div.classList.add("free"); } else { @@ -14986,6 +15327,7 @@ class HighlightEditor extends AnnotationEditor { } const highlightDiv = this.#highlightDiv = document.createElement("div"); div.append(highlightDiv); + highlightDiv.setAttribute("aria-hidden", "true"); highlightDiv.className = "internal"; highlightDiv.style.clipPath = this.#clipPathId; const [parentWidth, parentHeight] = this.parentDimensions; @@ -15029,16 +15371,32 @@ class HighlightEditor extends AnnotationEditor { } select() { super.select(); + if (!this.#outlineId) { + return; + } this.parent?.drawLayer.removeClass(this.#outlineId, "hovered"); this.parent?.drawLayer.addClass(this.#outlineId, "selected"); } unselect() { super.unselect(); + if (!this.#outlineId) { + return; + } this.parent?.drawLayer.removeClass(this.#outlineId, "selected"); if (!this.#isFreeHighlight) { this.#setCaret(false); } } + get _mustFixPosition() { + return !this.#isFreeHighlight; + } + show(visible = this._isVisible) { + super.show(visible); + if (this.parent) { + this.parent.drawLayer.show(this.#id, visible); + this.parent.drawLayer.show(this.#outlineId, visible); + } + } #getRotation() { return this.#isFreeHighlight ? this.rotation : 0; } @@ -15487,7 +15845,7 @@ class InkEditor extends AnnotationEditor { this.allRawPaths.push(currentPath); this.paths.push(bezier); this.bezierPath2D.push(path2D); - this.rebuild(); + this._uiManager.rebuild(this); }; const undo = () => { this.allRawPaths.pop(); @@ -16114,7 +16472,7 @@ class StampEditor extends AnnotationEditor { if (this.div === null) { return; } - if (this.#bitmapId) { + if (this.#bitmapId && this.#canvas === null) { this.#getBitmap(); } if (!this.isAttachedToDOM) { @@ -16126,7 +16484,7 @@ class StampEditor extends AnnotationEditor { this.div.focus(); } isEmpty() { - return !(this.#bitmapPromise || this.#bitmap || this.#bitmapUrl || this.#bitmapFile); + return !(this.#bitmapPromise || this.#bitmap || this.#bitmapUrl || this.#bitmapFile || this.#bitmapId); } get isResizable() { return true; @@ -16142,6 +16500,7 @@ class StampEditor extends AnnotationEditor { } super.render(); this.div.hidden = true; + this.addAltTextButton(); if (this.#bitmap) { this.#createCanvas(); } else { @@ -16186,7 +16545,6 @@ class StampEditor extends AnnotationEditor { this._reportTelemetry({ action: "inserted_image" }); - this.addAltTextButton(); if (this.#bitmapFileName) { canvas.setAttribute("aria-label", this.#bitmapFileName); } @@ -16404,9 +16762,9 @@ class AnnotationEditorLayer { #accessibilityManager; #allowClick = false; #annotationLayer = null; - #boundPointerup = this.pointerup.bind(this); - #boundPointerdown = this.pointerdown.bind(this); - #boundTextLayerPointerDown = this.#textLayerPointerDown.bind(this); + #boundPointerup = null; + #boundPointerdown = null; + #boundTextLayerPointerDown = null; #editorFocusTimeoutId = null; #editors = new Map(); #hadPointerDown = false; @@ -16448,6 +16806,9 @@ class AnnotationEditorLayer { get isEmpty() { return this.#editors.size === 0; } + get isInvisible() { + return this.isEmpty && this.#uiManager.getMode() === AnnotationEditorType.NONE; + } updateToolbar(mode) { this.#uiManager.updateToolbar(mode); } @@ -16519,6 +16880,7 @@ class AnnotationEditorLayer { this.#annotationLayer?.div.classList.toggle("disabled", !enabled); } enable() { + this.div.tabIndex = 0; this.togglePointerEvents(true); const annotationElementIds = new Set(); for (const editor of this.#editors.values()) { @@ -16549,6 +16911,7 @@ class AnnotationEditorLayer { } disable() { this.#isDisabling = true; + this.div.tabIndex = -1; this.togglePointerEvents(false); const hiddenAnnotationIds = new Set(); for (const editor of this.#editors.values()) { @@ -16597,14 +16960,18 @@ class AnnotationEditorLayer { this.#uiManager.setActiveEditor(editor); } enableTextSelection() { - if (this.#textLayer?.div) { + this.div.tabIndex = -1; + if (this.#textLayer?.div && !this.#boundTextLayerPointerDown) { + this.#boundTextLayerPointerDown = this.#textLayerPointerDown.bind(this); this.#textLayer.div.addEventListener("pointerdown", this.#boundTextLayerPointerDown); this.#textLayer.div.classList.add("highlighting"); } } disableTextSelection() { - if (this.#textLayer?.div) { + this.div.tabIndex = 0; + if (this.#textLayer?.div && this.#boundTextLayerPointerDown) { this.#textLayer.div.removeEventListener("pointerdown", this.#boundTextLayerPointerDown); + this.#boundTextLayerPointerDown = null; this.#textLayer.div.classList.remove("highlighting"); } } @@ -16617,6 +16984,7 @@ class AnnotationEditorLayer { if (event.button !== 0 || event.ctrlKey && isMac) { return; } + this.#uiManager.showAllEditors("highlight", true, true); this.#textLayer.div.classList.add("free"); HighlightEditor.startHighlighting(this, this.#uiManager.direction === "ltr", event); this.#textLayer.div.addEventListener("pointerup", () => { @@ -16628,12 +16996,22 @@ class AnnotationEditorLayer { } } enableClick() { + if (this.#boundPointerdown) { + return; + } + this.#boundPointerdown = this.pointerdown.bind(this); + this.#boundPointerup = this.pointerup.bind(this); this.div.addEventListener("pointerdown", this.#boundPointerdown); this.div.addEventListener("pointerup", this.#boundPointerup); } disableClick() { + if (!this.#boundPointerdown) { + return; + } this.div.removeEventListener("pointerdown", this.#boundPointerdown); this.div.removeEventListener("pointerup", this.#boundPointerup); + this.#boundPointerdown = null; + this.#boundPointerup = null; } attach(editor) { this.#editors.set(editor.id, editor); @@ -16678,6 +17056,9 @@ class AnnotationEditorLayer { } } add(editor) { + if (editor.parent === this && editor.isAttachedToDOM) { + return; + } this.changeParent(editor); this.#uiManager.addEditor(editor); this.attach(editor); @@ -16720,6 +17101,7 @@ class AnnotationEditorLayer { if (editor.needsToBeRebuilt()) { editor.parent ||= this; editor.rebuild(); + editor.show(); } else { this.add(editor); } @@ -16910,6 +17292,7 @@ class AnnotationEditorLayer { setLayerDimensions(this.div, viewport); for (const editor of this.#uiManager.getEditors(this.pageIndex)) { this.add(editor); + editor.rebuild(); } this.updateMode(); } @@ -16917,6 +17300,7 @@ class AnnotationEditorLayer { viewport }) { this.#uiManager.commitOrRemove(); + this.#cleanup(); const oldRotation = this.viewport.rotation; const rotation = viewport.rotation; this.viewport = viewport; @@ -16928,7 +17312,7 @@ class AnnotationEditorLayer { editor.rotate(rotation); } } - this.updateMode(); + this.addInkEditorIfNeeded(false); } get pageDimensions() { const { @@ -16990,6 +17374,7 @@ class DrawLayer { #createSVG(box) { const svg = DrawLayer._svgFactory.create(1, 1, true); this.#parent.append(svg); + svg.setAttribute("aria-hidden", true); DrawLayer.#setBox(svg, box); return svg; } @@ -17102,6 +17487,9 @@ class DrawLayer { updateBox(id, box) { DrawLayer.#setBox(this.#mapping.get(id), box); } + show(id, visible) { + this.#mapping.get(id).classList.toggle("hidden", !visible); + } rotate(id, angle) { this.#mapping.get(id).setAttribute("data-main-rotation", angle); } @@ -17146,8 +17534,8 @@ class DrawLayer { -const pdfjsVersion = "4.1.249"; -const pdfjsBuild = "d07f37f44"; +const pdfjsVersion = "4.1.342"; +const pdfjsBuild = "e384df6f1"; var __webpack_exports__AbortException = __webpack_exports__.AbortException; var __webpack_exports__AnnotationEditorLayer = __webpack_exports__.AnnotationEditorLayer; diff --git a/toolkit/components/pdfjs/content/build/pdf.scripting.mjs b/toolkit/components/pdfjs/content/build/pdf.scripting.mjs index d667676f1a..f590e83af3 100644 --- a/toolkit/components/pdfjs/content/build/pdf.scripting.mjs +++ b/toolkit/components/pdfjs/content/build/pdf.scripting.mjs @@ -3957,8 +3957,8 @@ function initSandbox(params) { ;// CONCATENATED MODULE: ./src/pdf.scripting.js -const pdfjsVersion = "4.1.249"; -const pdfjsBuild = "d07f37f44"; +const pdfjsVersion = "4.1.342"; +const pdfjsBuild = "e384df6f1"; globalThis.pdfjsScripting = { initSandbox: initSandbox }; diff --git a/toolkit/components/pdfjs/content/build/pdf.worker.mjs b/toolkit/components/pdfjs/content/build/pdf.worker.mjs index 8929572511..e2cd54cf56 100644 --- a/toolkit/components/pdfjs/content/build/pdf.worker.mjs +++ b/toolkit/components/pdfjs/content/build/pdf.worker.mjs @@ -94,7 +94,8 @@ const AnnotationEditorParamsType = { HIGHLIGHT_COLOR: 31, HIGHLIGHT_DEFAULT_COLOR: 32, HIGHLIGHT_THICKNESS: 33, - HIGHLIGHT_FREE: 34 + HIGHLIGHT_FREE: 34, + HIGHLIGHT_SHOW_ALL: 35 }; const PermissionFlag = { PRINT: 0x04, @@ -31442,17 +31443,25 @@ class PartialEvaluator { } const SMALL_IMAGE_DIMENSIONS = 200; if (isInline && !dict.has("SMask") && !dict.has("Mask") && w + h < SMALL_IMAGE_DIMENSIONS) { - const imageObj = new PDFImage({ - xref: this.xref, - res: resources, - image, - isInline, - pdfFunctionFactory: this._pdfFunctionFactory, - localColorSpaceCache - }); - imgData = await imageObj.createImageData(true, false); - operatorList.isOffscreenCanvasSupported = this.options.isOffscreenCanvasSupported; - operatorList.addImageOps(OPS.paintInlineImageXObject, [imgData], optionalContent); + try { + const imageObj = new PDFImage({ + xref: this.xref, + res: resources, + image, + isInline, + pdfFunctionFactory: this._pdfFunctionFactory, + localColorSpaceCache + }); + imgData = await imageObj.createImageData(true, false); + operatorList.isOffscreenCanvasSupported = this.options.isOffscreenCanvasSupported; + operatorList.addImageOps(OPS.paintInlineImageXObject, [imgData], optionalContent); + } catch (reason) { + const msg = `Unable to decode inline image: "${reason}".`; + if (!this.options.ignoreErrors) { + throw new Error(msg); + } + warn(msg); + } return; } let objId = `img_${this.idFactory.createObjId()}`, @@ -38663,14 +38672,9 @@ class Catalog { continue; } groupRefs.put(groupRef); - const group = this.xref.fetch(groupRef); - groups.push({ - id: groupRef.toString(), - name: typeof group.get("Name") === "string" ? stringToPDFString(group.get("Name")) : null, - intent: typeof group.get("Intent") === "string" ? stringToPDFString(group.get("Intent")) : null - }); + groups.push(this.#readOptionalContentGroup(groupRef)); } - config = this._readOptionalContentConfig(defaultConfig, groupRefs); + config = this.#readOptionalContentConfig(defaultConfig, groupRefs); config.groups = groups; } catch (ex) { if (ex instanceof MissingDataException) { @@ -38680,7 +38684,62 @@ class Catalog { } return shadow(this, "optionalContentConfig", config); } - _readOptionalContentConfig(config, contentGroupRefs) { + #readOptionalContentGroup(groupRef) { + const group = this.xref.fetch(groupRef); + const obj = { + id: groupRef.toString(), + name: null, + intent: null, + usage: { + print: null, + view: null + } + }; + const name = group.get("Name"); + if (typeof name === "string") { + obj.name = stringToPDFString(name); + } + let intent = group.getArray("Intent"); + if (!Array.isArray(intent)) { + intent = [intent]; + } + if (intent.every(i => i instanceof Name)) { + obj.intent = intent.map(i => i.name); + } + const usage = group.get("Usage"); + if (!(usage instanceof Dict)) { + return obj; + } + const usageObj = obj.usage; + const print = usage.get("Print"); + if (print instanceof Dict) { + const printState = print.get("PrintState"); + if (printState instanceof Name) { + switch (printState.name) { + case "ON": + case "OFF": + usageObj.print = { + printState: printState.name + }; + } + } + } + const view = usage.get("View"); + if (view instanceof Dict) { + const viewState = view.get("ViewState"); + if (viewState instanceof Name) { + switch (viewState.name) { + case "ON": + case "OFF": + usageObj.view = { + viewState: viewState.name + }; + } + } + } + return obj; + } + #readOptionalContentConfig(config, contentGroupRefs) { function parseOnOff(refs) { const onParsed = []; if (Array.isArray(refs)) { @@ -55084,17 +55143,24 @@ class Page { })); } const sortedAnnotations = []; - let popupAnnotations; + let popupAnnotations, widgetAnnotations; for (const annotation of await Promise.all(annotationPromises)) { if (!annotation) { continue; } + if (annotation instanceof WidgetAnnotation) { + (widgetAnnotations ||= []).push(annotation); + continue; + } if (annotation instanceof PopupAnnotation) { (popupAnnotations ||= []).push(annotation); continue; } sortedAnnotations.push(annotation); } + if (widgetAnnotations) { + sortedAnnotations.push(...widgetAnnotations); + } if (popupAnnotations) { sortedAnnotations.push(...popupAnnotations); } @@ -56659,7 +56725,7 @@ class WorkerMessageHandler { docId, apiVersion } = docParams; - const workerVersion = "4.1.249"; + const workerVersion = "4.1.342"; if (apiVersion !== workerVersion) { throw new Error(`The API version "${apiVersion}" does not match ` + `the Worker version "${workerVersion}".`); } @@ -57221,8 +57287,8 @@ if (typeof window === "undefined" && !isNodeJS && typeof self !== "undefined" && ;// CONCATENATED MODULE: ./src/pdf.worker.js -const pdfjsVersion = "4.1.249"; -const pdfjsBuild = "d07f37f44"; +const pdfjsVersion = "4.1.342"; +const pdfjsBuild = "e384df6f1"; var __webpack_exports__WorkerMessageHandler = __webpack_exports__.WorkerMessageHandler; export { __webpack_exports__WorkerMessageHandler as WorkerMessageHandler }; diff --git a/toolkit/components/pdfjs/content/web/images/gv-toolbarButton-openinapp.svg b/toolkit/components/pdfjs/content/web/images/gv-toolbarButton-openinapp.svg deleted file mode 100644 index 80ec891aad..0000000000 --- a/toolkit/components/pdfjs/content/web/images/gv-toolbarButton-openinapp.svg +++ /dev/null @@ -1,11 +0,0 @@ -<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M4 4.5H6.5V7H4V4.5Z" fill="black"/> -<path d="M6.5 10.5H4V13H6.5V10.5Z" fill="black"/> -<path d="M13.25 10.5H10.75V13H13.25V10.5Z" fill="black"/> -<path d="M17.5 10.5H20V13H17.5V10.5Z" fill="black"/> -<path d="M6.5 16.5H4V19H6.5V16.5Z" fill="black"/> -<path d="M10.75 16.5H13.25V19H10.75V16.5Z" fill="black"/> -<path d="M20 16.5H17.5V19H20V16.5Z" fill="black"/> -<path d="M13.25 4.5H10.75V7H13.25V4.5Z" fill="black"/> -<path d="M17.5 4.5H20V7H17.5V4.5Z" fill="black"/> -</svg> diff --git a/toolkit/components/pdfjs/content/web/viewer-geckoview.css b/toolkit/components/pdfjs/content/web/viewer-geckoview.css index 16e27e4eac..c336b4f7fc 100644 --- a/toolkit/components/pdfjs/content/web/viewer-geckoview.css +++ b/toolkit/components/pdfjs/content/web/viewer-geckoview.css @@ -23,7 +23,6 @@ text-size-adjust:none; forced-color-adjust:none; transform-origin:0 0; - z-index:2; caret-color:CanvasText; &.highlighting{ @@ -162,7 +161,6 @@ left:0; pointer-events:none; transform-origin:0 0; - z-index:3; &[data-main-rotation="90"] .norotate{ transform:rotate(270deg) translateX(-100%); @@ -817,7 +815,6 @@ overflow:hidden; width:100%; height:100%; - z-index:1; } .pdfViewer .page{ @@ -947,7 +944,6 @@ --toolbar-fg-color:#15141a; --toolbarButton-download-icon:url(images/gv-toolbarButton-download.svg); - --toolbarButton-openinapp-icon:url(images/gv-toolbarButton-openinapp.svg); } :root:dir(rtl){ @@ -1140,10 +1136,6 @@ body{ mask-image:var(--toolbarButton-download-icon); } -#openInApp::before{ - mask-image:var(--toolbarButton-openinapp-icon); -} - .dialogButton{ width:auto; margin:3px 4px 2px !important; diff --git a/toolkit/components/pdfjs/content/web/viewer-geckoview.html b/toolkit/components/pdfjs/content/web/viewer-geckoview.html index 1c0296a481..ba52e298ed 100644 --- a/toolkit/components/pdfjs/content/web/viewer-geckoview.html +++ b/toolkit/components/pdfjs/content/web/viewer-geckoview.html @@ -45,9 +45,6 @@ See https://github.com/adobe-type-tools/cmap-resources <button id="download" class="toolbarButton" title="Download" tabindex="31" data-l10n-id="pdfjs-download-button"> <span data-l10n-id="pdfjs-download-button-label">Download</span> </button> - <button id="openInApp" class="toolbarButton" title="Open in app" tabindex="32" data-l10n-id="pdfjs-open-in-app-button"> - <span data-l10n-id="pdfjs-open-in-app-button-label">Open in app</span> - </button> </div> <div id="viewerContainer" tabindex="0"> diff --git a/toolkit/components/pdfjs/content/web/viewer-geckoview.mjs b/toolkit/components/pdfjs/content/web/viewer-geckoview.mjs index 8ba8147a9f..866c4a405a 100644 --- a/toolkit/components/pdfjs/content/web/viewer-geckoview.mjs +++ b/toolkit/components/pdfjs/content/web/viewer-geckoview.mjs @@ -606,6 +606,10 @@ const defaultOptions = { value: false, kind: OptionKind.VIEWER + OptionKind.PREFERENCE }, + enableHighlightFloatingButton: { + value: false, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE + }, enableML: { value: false, kind: OptionKind.VIEWER + OptionKind.PREFERENCE @@ -651,7 +655,7 @@ const defaultOptions = { kind: OptionKind.VIEWER }, maxCanvasPixels: { - value: 16777216, + value: 2 ** 25, kind: OptionKind.VIEWER }, forcePageColors: { @@ -768,28 +772,20 @@ class AppOptions { constructor() { throw new Error("Cannot initialize AppOptions."); } + static getCompat(name) { + return compatibilityParams[name] ?? undefined; + } static get(name) { - const userOption = userOptions[name]; - if (userOption !== undefined) { - return userOption; - } - const defaultOption = defaultOptions[name]; - if (defaultOption !== undefined) { - return compatibilityParams[name] ?? defaultOption.value; - } - return undefined; + return userOptions[name] ?? compatibilityParams[name] ?? defaultOptions[name]?.value ?? undefined; } - static getAll(kind = null) { + static getAll(kind = null, defaultOnly = false) { const options = Object.create(null); for (const name in defaultOptions) { const defaultOption = defaultOptions[name]; - if (kind) { - if (!(kind & defaultOption.kind)) { - continue; - } + if (kind && !(kind & defaultOption.kind)) { + continue; } - const userOption = userOptions[name]; - options[name] = userOption !== undefined ? userOption : compatibilityParams[name] ?? defaultOption.value; + options[name] = defaultOnly ? defaultOption.value : userOptions[name] ?? compatibilityParams[name] ?? defaultOption.value; } return options; } @@ -1112,30 +1108,7 @@ class PDFLinkService { if (pdfDocument !== this.pdfDocument) { return; } - let operator; - for (const elem of action.state) { - switch (elem) { - case "ON": - case "OFF": - case "Toggle": - operator = elem; - continue; - } - switch (operator) { - case "ON": - optionalContentConfig.setVisibility(elem, true); - break; - case "OFF": - optionalContentConfig.setVisibility(elem, false); - break; - case "Toggle": - const group = optionalContentConfig.getGroup(elem); - if (group) { - optionalContentConfig.setVisibility(elem, !group.visible); - } - break; - } - } + optionalContentConfig.setOCGState(action); this.pdfViewer.optionalContentConfigPromise = Promise.resolve(optionalContentConfig); } cachePageRef(pageNum, pageRef) { @@ -1423,7 +1396,7 @@ class BaseExternalServices { } updateFindControlState(data) {} updateFindMatchesCount(data) {} - initPassiveLoading(callbacks) {} + initPassiveLoading() {} reportTelemetry(data) {} async createL10n() { throw new Error("Not implemented: createL10n"); @@ -1440,6 +1413,16 @@ class BaseExternalServices { ;// CONCATENATED MODULE: ./web/preferences.js class BasePreferences { + #browserDefaults = Object.freeze({ + canvasMaxAreaInBytes: -1, + isInAutomation: false, + supportsCaretBrowsingMode: false, + supportsDocumentFonts: true, + supportsIntegratedFind: false, + supportsMouseWheelZoomCtrlKey: true, + supportsMouseWheelZoomMetaKey: true, + supportsPinchToZoom: true + }); #defaults = Object.freeze({ annotationEditorMode: 0, annotationMode: 2, @@ -1448,6 +1431,7 @@ class BasePreferences { defaultZoomValue: "", disablePageLabels: false, enableHighlightEditor: false, + enableHighlightFloatingButton: false, enableML: false, enablePermissions: false, enablePrintAutoRotate: true, @@ -1482,26 +1466,19 @@ class BasePreferences { browserPrefs, prefs }) => { - const BROWSER_PREFS = { - canvasMaxAreaInBytes: -1, - isInAutomation: false, - supportsCaretBrowsingMode: false, - supportsDocumentFonts: true, - supportsIntegratedFind: false, - supportsMouseWheelZoomCtrlKey: true, - supportsMouseWheelZoomMetaKey: true, - supportsPinchToZoom: true - }; const options = Object.create(null); - for (const [name, defaultVal] of Object.entries(BROWSER_PREFS)) { + for (const [name, val] of Object.entries(this.#browserDefaults)) { const prefVal = browserPrefs?.[name]; - options[name] = typeof prefVal === typeof defaultVal ? prefVal : defaultVal; + options[name] = typeof prefVal === typeof val ? prefVal : val; } - for (const [name, defaultVal] of Object.entries(this.#defaults)) { + for (const [name, val] of Object.entries(this.#defaults)) { const prefVal = prefs?.[name]; - options[name] = this.#prefs[name] = typeof prefVal === typeof defaultVal ? prefVal : defaultVal; + options[name] = this.#prefs[name] = typeof prefVal === typeof val ? prefVal : val; } AppOptions.setAll(options, true); + window.addEventListener("updatedPreference", evt => { + this.#updatePref(evt.detail); + }); }); } async _writeToStorage(prefObj) { @@ -1510,6 +1487,24 @@ class BasePreferences { async _readFromStorage(prefObj) { throw new Error("Not implemented: _readFromStorage"); } + #updatePref({ + name, + value + }) { + if (name in this.#browserDefaults) { + if (typeof value !== typeof this.#browserDefaults[name]) { + return; + } + } else if (name in this.#defaults) { + if (typeof value !== typeof this.#defaults[name]) { + return; + } + this.#prefs[name] = value; + } else { + return; + } + AppOptions.set(name, value); + } async reset() { throw new Error("Please use `about:config` to change preferences."); } @@ -1517,12 +1512,7 @@ class BasePreferences { throw new Error("Please use `about:config` to change preferences."); } async get(name) { - await this.#initializedPromise; - const defaultValue = this.#defaults[name]; - if (defaultValue === undefined) { - throw new Error(`Get preference: "${name}" is undefined.`); - } - return this.#prefs[name] ?? defaultValue; + throw new Error("Not implemented: get"); } get initializedPromise() { return this.#initializedPromise; @@ -1805,8 +1795,7 @@ class Preferences extends BasePreferences { try { const hasUnchangedAnnotations = pdfDocument.annotationStorage.size === 0; const hasWillPrint = pdfViewer.enableScripting && !!(await pdfDocument.getJSActions())?.WillPrint; - const hasUnchangedOptionalContent = (await pdfViewer.optionalContentConfigPromise).hasInitialVisibility; - result = hasUnchangedAnnotations && !hasWillPrint && hasUnchangedOptionalContent; + result = hasUnchangedAnnotations && !hasWillPrint; } catch { console.warn("Unable to check if the document can be downloaded."); } @@ -1860,7 +1849,7 @@ class ExternalServices extends BaseExternalServices { updateFindMatchesCount(data) { FirefoxCom.request("updateFindMatchesCount", data); } - initPassiveLoading(callbacks) { + initPassiveLoading() { let pdfDataRangeTransport; window.addEventListener("message", function windowMessage(e) { if (e.source !== null) { @@ -1874,11 +1863,13 @@ class ExternalServices extends BaseExternalServices { switch (args.pdfjsLoadAction) { case "supportsRangedLoading": if (args.done && !args.data) { - callbacks.onError(); + viewerApp._documentError(null); break; } pdfDataRangeTransport = new FirefoxComDataRangeTransport(args.length, args.data, args.done, args.filename); - callbacks.onOpenWithTransport(pdfDataRangeTransport); + viewerApp.open({ + range: pdfDataRangeTransport + }); break; case "range": pdfDataRangeTransport.onDataRange(args.begin, args.chunk); @@ -1894,21 +1885,26 @@ class ExternalServices extends BaseExternalServices { pdfDataRangeTransport?.onDataProgressiveDone(); break; case "progress": - callbacks.onProgress(args.loaded, args.total); + viewerApp.progress(args.loaded / args.total); break; case "complete": if (!args.data) { - callbacks.onError(args.errorCode); + viewerApp._documentError(null, { + message: args.errorCode + }); break; } - callbacks.onOpenWithData(args.data, args.filename); + viewerApp.open({ + data: args.data, + filename: args.filename + }); break; } }); FirefoxCom.request("initPassiveLoading", null); } reportTelemetry(data) { - FirefoxCom.request("reportTelemetry", JSON.stringify(data)); + FirefoxCom.request("reportTelemetry", data); } updateEditorStates(data) { FirefoxCom.request("updateEditorStates", data); @@ -3671,14 +3667,15 @@ class FirefoxPrintService { pagesOverview, printContainer, printResolution, - optionalContentConfigPromise = null, printAnnotationStoragePromise = null }) { this.pdfDocument = pdfDocument; this.pagesOverview = pagesOverview; this.printContainer = printContainer; this._printResolution = printResolution || 150; - this._optionalContentConfigPromise = optionalContentConfigPromise || pdfDocument.getOptionalContentConfig(); + this._optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({ + intent: "print" + }); this._printAnnotationStoragePromise = printAnnotationStoragePromise || Promise.resolve(); } layout() { @@ -4206,10 +4203,10 @@ class PDFScriptingManager { class AnnotationEditorLayerBuilder { #annotationLayer = null; #drawLayer = null; + #onAppend = null; #textLayer = null; #uiManager; constructor(options) { - this.pageDiv = options.pageDiv; this.pdfPage = options.pdfPage; this.accessibilityManager = options.accessibilityManager; this.l10n = options.l10n; @@ -4220,6 +4217,7 @@ class AnnotationEditorLayerBuilder { this.#annotationLayer = options.annotationLayer || null; this.#textLayer = options.textLayer || null; this.#drawLayer = options.drawLayer || null; + this.#onAppend = options.onAppend || null; } async render(viewport, intent = "display") { if (intent !== "display") { @@ -4240,10 +4238,9 @@ class AnnotationEditorLayerBuilder { } const div = this.div = document.createElement("div"); div.className = "annotationEditorLayer"; - div.tabIndex = 0; div.hidden = true; div.dir = this.#uiManager.direction; - this.pageDiv.append(div); + this.#onAppend?.(div); this.annotationEditorLayer = new AnnotationEditorLayer({ uiManager: this.#uiManager, div, @@ -4269,9 +4266,7 @@ class AnnotationEditorLayerBuilder { if (!this.div) { return; } - this.pageDiv = null; this.annotationEditorLayer.destroy(); - this.div.remove(); } hide() { if (!this.div) { @@ -4280,7 +4275,7 @@ class AnnotationEditorLayerBuilder { this.div.hidden = true; } show() { - if (!this.div || this.annotationEditorLayer.isEmpty) { + if (!this.div || this.annotationEditorLayer.isInvisible) { return; } this.div.hidden = false; @@ -4291,9 +4286,9 @@ class AnnotationEditorLayerBuilder { class AnnotationLayerBuilder { + #onAppend = null; #onPresentationModeChanged = null; constructor({ - pageDiv, pdfPage, linkService, downloadManager, @@ -4304,9 +4299,9 @@ class AnnotationLayerBuilder { hasJSActionsPromise = null, fieldObjectsPromise = null, annotationCanvasMap = null, - accessibilityManager = null + accessibilityManager = null, + onAppend = null }) { - this.pageDiv = pageDiv; this.pdfPage = pdfPage; this.linkService = linkService; this.downloadManager = downloadManager; @@ -4318,6 +4313,7 @@ class AnnotationLayerBuilder { this._fieldObjectsPromise = fieldObjectsPromise || Promise.resolve(null); this._annotationCanvasMap = annotationCanvasMap; this._accessibilityManager = accessibilityManager; + this.#onAppend = onAppend; this.annotationLayer = null; this.div = null; this._cancelled = false; @@ -4343,7 +4339,7 @@ class AnnotationLayerBuilder { } const div = this.div = document.createElement("div"); div.className = "annotationLayer"; - this.pageDiv.append(div); + this.#onAppend?.(div); if (annotations.length === 0) { this.hide(); return; @@ -4937,13 +4933,15 @@ class TextHighlighter { class TextLayerBuilder { #enablePermissions = false; + #onAppend = null; #rotation = 0; #scale = 0; #textContentSource = null; constructor({ highlighter = null, accessibilityManager = null, - enablePermissions = false + enablePermissions = false, + onAppend = null }) { this.textContentItemsStr = []; this.renderingDone = false; @@ -4953,8 +4951,9 @@ class TextLayerBuilder { this.highlighter = highlighter; this.accessibilityManager = accessibilityManager; this.#enablePermissions = enablePermissions === true; - this.onAppend = null; + this.#onAppend = onAppend; this.div = document.createElement("div"); + this.div.tabIndex = 0; this.div.className = "textLayer"; } #finishRendering() { @@ -5009,7 +5008,7 @@ class TextLayerBuilder { this.#finishRendering(); this.#scale = scale; this.#rotation = rotation; - this.onAppend(this.div); + this.#onAppend?.(this.div); this.highlighter?.enable(); this.accessibilityManager?.enable(); } @@ -5083,8 +5082,8 @@ class TextLayerBuilder { -const MAX_CANVAS_PIXELS = compatibilityParams.maxCanvasPixels || 16777216; const DEFAULT_LAYER_PROPERTIES = null; +const LAYERS_ORDER = new Map([["canvasWrapper", 0], ["textLayer", 1], ["annotationLayer", 2], ["annotationEditorLayer", 3], ["xfaLayer", 3]]); class PDFPageView { #annotationMode = AnnotationMode.ENABLE_FORMS; #hasRestrictedScaling = false; @@ -5100,6 +5099,7 @@ class PDFPageView { regularAnnotations: true }; #viewportMap = new WeakMap(); + #layers = [null, null, null, null]; constructor(options) { const container = options.container; const defaultViewport = options.defaultViewport; @@ -5116,7 +5116,7 @@ class PDFPageView { this.#textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE; this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS; this.imageResourcesPath = options.imageResourcesPath || ""; - this.maxCanvasPixels = options.maxCanvasPixels ?? MAX_CANVAS_PIXELS; + this.maxCanvasPixels = options.maxCanvasPixels ?? (AppOptions.getCompat("maxCanvasPixels") || 2 ** 25); this.pageColors = options.pageColors || null; this.eventBus = options.eventBus; this.renderingQueue = options.renderingQueue; @@ -5143,6 +5143,23 @@ class PDFPageView { this.#setDimensions(); container?.append(div); } + #addLayer(div, name) { + const pos = LAYERS_ORDER.get(name); + const oldDiv = this.#layers[pos]; + this.#layers[pos] = div; + if (oldDiv) { + oldDiv.replaceWith(div); + return; + } + for (let i = pos - 1; i >= 0; i--) { + const layer = this.#layers[i]; + if (layer) { + layer.after(div); + return; + } + } + this.div.prepend(div); + } get renderingState() { return this.#renderingState; } @@ -5256,7 +5273,7 @@ class PDFPageView { } finally { if (this.xfaLayer?.div) { this.l10n.pause(); - this.div.append(this.xfaLayer.div); + this.#addLayer(this.xfaLayer.div, "xfaLayer"); this.l10n.resume(); } this.eventBus.dispatch("xfalayerrendered", { @@ -5368,6 +5385,10 @@ class PDFPageView { continue; } node.remove(); + const layerIndex = this.#layers.indexOf(node); + if (layerIndex >= 0) { + this.#layers[layerIndex] = null; + } } div.removeAttribute("data-loaded"); if (annotationLayerNode) { @@ -5627,19 +5648,20 @@ class PDFPageView { this.renderingState = RenderingStates.RUNNING; const canvasWrapper = document.createElement("div"); canvasWrapper.classList.add("canvasWrapper"); - div.append(canvasWrapper); + canvasWrapper.setAttribute("aria-hidden", true); + this.#addLayer(canvasWrapper, "canvasWrapper"); if (!this.textLayer && this.#textLayerMode !== TextLayerMode.DISABLE && !pdfPage.isPureXfa) { this._accessibilityManager ||= new TextAccessibilityManager(); this.textLayer = new TextLayerBuilder({ highlighter: this._textHighlighter, accessibilityManager: this._accessibilityManager, - enablePermissions: this.#textLayerMode === TextLayerMode.ENABLE_PERMISSIONS + enablePermissions: this.#textLayerMode === TextLayerMode.ENABLE_PERMISSIONS, + onAppend: textLayerDiv => { + this.l10n.pause(); + this.#addLayer(textLayerDiv, "textLayer"); + this.l10n.resume(); + } }); - this.textLayer.onAppend = textLayerDiv => { - this.l10n.pause(); - this.div.append(textLayerDiv); - this.l10n.resume(); - }; } if (!this.annotationLayer && this.#annotationMode !== AnnotationMode.DISABLE) { const { @@ -5652,7 +5674,6 @@ class PDFPageView { } = this.#layerProperties; this._annotationCanvasMap ||= new Map(); this.annotationLayer = new AnnotationLayerBuilder({ - pageDiv: div, pdfPage, annotationStorage, imageResourcesPath: this.imageResourcesPath, @@ -5663,7 +5684,10 @@ class PDFPageView { hasJSActionsPromise, fieldObjectsPromise, annotationCanvasMap: this._annotationCanvasMap, - accessibilityManager: this._accessibilityManager + accessibilityManager: this._accessibilityManager, + onAppend: annotationLayerDiv => { + this.#addLayer(annotationLayerDiv, "annotationLayer"); + } }); } const renderContinueCallback = cont => { @@ -5752,13 +5776,15 @@ class PDFPageView { if (!this.annotationEditorLayer) { this.annotationEditorLayer = new AnnotationEditorLayerBuilder({ uiManager: annotationEditorUIManager, - pageDiv: div, pdfPage, l10n, accessibilityManager: this._accessibilityManager, annotationLayer: this.annotationLayer?.annotationLayer, textLayer: this.textLayer, - drawLayer: this.drawLayer.getDrawLayer() + drawLayer: this.drawLayer.getDrawLayer(), + onAppend: annotationEditorLayerDiv => { + this.#addLayer(annotationEditorLayerDiv, "annotationEditorLayer"); + } }); } this.#renderAnnotationEditorLayer(); @@ -5883,6 +5909,7 @@ class PDFViewer { #annotationMode = AnnotationMode.ENABLE_FORMS; #containerTopLeft = null; #copyCallbackBound = null; + #enableHighlightFloatingButton = false; #enablePermissions = false; #mlManager = null; #getAllTextInProgress = false; @@ -5895,7 +5922,7 @@ class PDFViewer { #scaleTimeoutId = null; #textLayerMode = TextLayerMode.ENABLE; constructor(options) { - const viewerVersion = "4.1.249"; + const viewerVersion = "4.1.342"; if (version !== viewerVersion) { throw new Error(`The API version "${version}" does not match the Viewer version "${viewerVersion}".`); } @@ -5915,6 +5942,7 @@ class PDFViewer { this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS; this.#annotationEditorMode = options.annotationEditorMode ?? AnnotationEditorType.NONE; this.#annotationEditorHighlightColors = options.annotationEditorHighlightColors || null; + this.#enableHighlightFloatingButton = options.enableHighlightFloatingButton === true; this.imageResourcesPath = options.imageResourcesPath || ""; this.enablePrintAutoRotate = options.enablePrintAutoRotate || false; this.maxCanvasPixels = options.maxCanvasPixels; @@ -6225,7 +6253,9 @@ class PDFViewer { } const pagesCount = pdfDocument.numPages; const firstPagePromise = pdfDocument.getPage(1); - const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig(); + const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({ + intent: "display" + }); const permissionsPromise = this.#enablePermissions ? pdfDocument.getPermissions() : Promise.resolve(); if (pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE) { console.warn("Forcing PAGE-scrolling for performance reasons, given the length of the document."); @@ -6281,7 +6311,7 @@ class PDFViewer { if (pdfDocument.isPureXfa) { console.warn("Warning: XFA-editing is not implemented."); } else if (isValidAnnotationEditorMode(mode)) { - this.#annotationEditorUIManager = new AnnotationEditorUIManager(this.container, this.viewer, this.#altTextManager, this.eventBus, pdfDocument, this.pageColors, this.#annotationEditorHighlightColors, this.#mlManager); + this.#annotationEditorUIManager = new AnnotationEditorUIManager(this.container, this.viewer, this.#altTextManager, this.eventBus, pdfDocument, this.pageColors, this.#annotationEditorHighlightColors, this.#enableHighlightFloatingButton, this.#mlManager); this.eventBus.dispatch("annotationeditoruimanager", { source: this, uiManager: this.#annotationEditorUIManager @@ -6942,7 +6972,9 @@ class PDFViewer { } if (!this._optionalContentConfigPromise) { console.error("optionalContentConfigPromise: Not initialized yet."); - return this.pdfDocument.getOptionalContentConfig(); + return this.pdfDocument.getOptionalContentConfig({ + intent: "display" + }); } return this._optionalContentConfigPromise; } @@ -7281,10 +7313,6 @@ class Toolbar { element: options.download, eventName: "download", nimbusName: "download-button" - }, { - element: options.openInApp, - eventName: "openinexternalapp", - nimbusName: "open-in-app-button" }]; if (nimbusData) { this.#buttons = []; @@ -7645,6 +7673,7 @@ const PDFViewerApplication = { annotationMode: AppOptions.get("annotationMode"), annotationEditorMode, annotationEditorHighlightColors: AppOptions.get("highlightEditorColors"), + enableHighlightFloatingButton: AppOptions.get("enableHighlightFloatingButton"), imageResourcesPath: AppOptions.get("imageResourcesPath"), enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"), maxCanvasPixels: AppOptions.get("maxCanvasPixels"), @@ -7784,40 +7813,8 @@ const PDFViewerApplication = { if (this.supportsIntegratedFind) { appConfig.toolbar?.viewFind?.classList.add("hidden"); } - this.initPassiveLoading(file); - const { - mainContainer - } = appConfig; - ({ - scrollTop: this._lastScrollTop, - scrollLeft: this._lastScrollLeft - } = mainContainer); - const scroll = () => { - if (this._lastScrollTop === mainContainer.scrollTop && this._lastScrollLeft === mainContainer.scrollLeft) { - return; - } - mainContainer.removeEventListener("scroll", scroll, { - passive: true - }); - this._isScrolling = true; - const scrollend = () => { - ({ - scrollTop: this._lastScrollTop, - scrollLeft: this._lastScrollLeft - } = mainContainer); - this._isScrolling = false; - mainContainer.addEventListener("scroll", scroll, { - passive: true - }); - mainContainer.removeEventListener("scrollend", scrollend); - mainContainer.removeEventListener("blur", scrollend); - }; - mainContainer.addEventListener("scrollend", scrollend); - mainContainer.addEventListener("blur", scrollend); - }; - mainContainer.addEventListener("scroll", scroll, { - passive: true - }); + this.setTitleUsingUrl(file, file); + this.externalServices.initPassiveLoading(); }, get externalServices() { return shadow(this, "externalServices", new ExternalServices()); @@ -7890,45 +7887,12 @@ const PDFViewerApplication = { return shadow(this, "supportsMouseWheelZoomMetaKey", AppOptions.get("supportsMouseWheelZoomMetaKey")); }, get supportsCaretBrowsingMode() { - return shadow(this, "supportsCaretBrowsingMode", AppOptions.get("supportsCaretBrowsingMode")); + return AppOptions.get("supportsCaretBrowsingMode"); }, moveCaret(isUp, select) { this._caretBrowsing ||= new CaretBrowsingMode(this.appConfig.mainContainer, this.appConfig.viewerContainer, this.appConfig.toolbar?.container); this._caretBrowsing.moveCaret(isUp, select); }, - initPassiveLoading(file) { - this.setTitleUsingUrl(file, file); - this.externalServices.initPassiveLoading({ - onOpenWithTransport: range => { - this.open({ - range - }); - }, - onOpenWithData: (data, contentDispositionFilename) => { - if (isPdfFile(contentDispositionFilename)) { - this._contentDispositionFilename = contentDispositionFilename; - } - this.open({ - data - }); - }, - onOpenWithURL: (url, length, originalUrl) => { - this.open({ - url, - length, - originalUrl - }); - }, - onError: err => { - this.l10n.get("pdfjs-loading-error").then(msg => { - this._documentError(msg, err); - }); - }, - onProgress: (loaded, total) => { - this.progress(loaded / total); - } - }); - }, setTitleUsingUrl(url = "", downloadUrl = null) { this.url = url; this.baseUrl = url.split("#", 1)[0]; @@ -8016,6 +7980,9 @@ const PDFViewerApplication = { } const workerParams = AppOptions.getAll(OptionKind.WORKER); Object.assign(GlobalWorkerOptions, workerParams); + if (args.data && isPdfFile(args.filename)) { + this._contentDispositionFilename = args.filename; + } AppOptions.set("docBaseUrl", this.baseUrl); const apiParams = AppOptions.getAll(OptionKind.API); const loadingTask = getDocument({ @@ -8051,10 +8018,9 @@ const PDFViewerApplication = { } else if (reason instanceof UnexpectedResponseException) { key = "pdfjs-unexpected-response-error"; } - return this.l10n.get(key).then(msg => { - this._documentError(msg, { - message: reason?.message - }); + return this._documentError(key, { + message: reason.message + }).then(() => { throw reason; }); }); @@ -8118,21 +8084,17 @@ const PDFViewerApplication = { this.download(options); } }, - openInExternalApp() { - this.downloadOrSave({ - openInExternalApp: true - }); - }, - _documentError(message, moreInfo = null) { + async _documentError(key, moreInfo = null) { this._unblockDocumentLoadEvent(); - this._otherError(message, moreInfo); + const message = await this._otherError(key || "pdfjs-loading-error", moreInfo); this.eventBus.dispatch("documenterror", { source: this, message, reason: moreInfo?.message ?? null }); }, - _otherError(message, moreInfo = null) { + async _otherError(key, moreInfo = null) { + const message = await this.l10n.get(key); const moreInfoText = [`PDF.js v${version || "?"} (build: ${build || "?"})`]; if (moreInfo) { moreInfoText.push(`Message: ${moreInfo.message}`); @@ -8148,6 +8110,7 @@ const PDFViewerApplication = { } } console.error(`${message}\n\n${moreInfoText.join("\n")}`); + return message; }, progress(level) { if (!this.loadingBar || this.downloadComplete) { @@ -8272,10 +8235,8 @@ const PDFViewerApplication = { this._unblockDocumentLoadEvent(); this._initializeAutoPrint(pdfDocument, openActionPromise); }, reason => { - this.l10n.get("pdfjs-loading-error").then(msg => { - this._documentError(msg, { - message: reason?.message - }); + this._documentError("pdfjs-loading-error", { + message: reason.message }); }); onePageRendered.then(data => { @@ -8521,9 +8482,7 @@ const PDFViewerApplication = { return; } if (!this.supportsPrinting) { - this.l10n.get("pdfjs-printing-not-supported").then(msg => { - this._otherError(msg); - }); + this._otherError("pdfjs-printing-not-supported"); return; } if (!this.pdfViewer.pageViewsReady) { @@ -8537,7 +8496,6 @@ const PDFViewerApplication = { pagesOverview: this.pdfViewer.getPagesOverview(), printContainer: this.appConfig.printContainer, printResolution: AppOptions.get("printResolution"), - optionalContentConfigPromise: this.pdfViewer.optionalContentConfigPromise, printAnnotationStoragePromise: this._printAnnotationStoragePromise }); this.forceRendering(); @@ -8606,7 +8564,6 @@ const PDFViewerApplication = { eventBus._on("switchannotationeditorparams", webViewerSwitchAnnotationEditorParams); eventBus._on("print", webViewerPrint); eventBus._on("download", webViewerDownload); - eventBus._on("openinexternalapp", webViewerOpenInExternalApp); eventBus._on("firstpage", webViewerFirstPage); eventBus._on("lastpage", webViewerLastPage); eventBus._on("nextpage", webViewerNextPage); @@ -8638,7 +8595,10 @@ const PDFViewerApplication = { bindWindowEvents() { const { eventBus, - _boundEvents + _boundEvents, + appConfig: { + mainContainer + } } = this; function addWindowResolutionChange(evt = null) { if (evt) { @@ -8698,12 +8658,79 @@ const PDFViewerApplication = { window.addEventListener("beforeprint", _boundEvents.windowBeforePrint); window.addEventListener("afterprint", _boundEvents.windowAfterPrint); window.addEventListener("updatefromsandbox", _boundEvents.windowUpdateFromSandbox); + ({ + scrollTop: this._lastScrollTop, + scrollLeft: this._lastScrollLeft + } = mainContainer); + const scrollend = _boundEvents.mainContainerScrollend = () => { + ({ + scrollTop: this._lastScrollTop, + scrollLeft: this._lastScrollLeft + } = mainContainer); + this._isScrolling = false; + mainContainer.addEventListener("scroll", scroll, { + passive: true + }); + mainContainer.removeEventListener("scrollend", scrollend); + mainContainer.removeEventListener("blur", scrollend); + }; + const scroll = _boundEvents.mainContainerScroll = () => { + if (this._isCtrlKeyDown || this._lastScrollTop === mainContainer.scrollTop && this._lastScrollLeft === mainContainer.scrollLeft) { + return; + } + mainContainer.removeEventListener("scroll", scroll, { + passive: true + }); + this._isScrolling = true; + mainContainer.addEventListener("scrollend", scrollend); + mainContainer.addEventListener("blur", scrollend); + }; + mainContainer.addEventListener("scroll", scroll, { + passive: true + }); }, unbindEvents() { throw new Error("Not implemented: unbindEvents"); }, unbindWindowEvents() { - throw new Error("Not implemented: unbindWindowEvents"); + const { + _boundEvents, + appConfig: { + mainContainer + } + } = this; + window.removeEventListener("visibilitychange", webViewerVisibilityChange); + window.removeEventListener("wheel", webViewerWheel, { + passive: false + }); + window.removeEventListener("touchstart", webViewerTouchStart, { + passive: false + }); + window.removeEventListener("touchmove", webViewerTouchMove, { + passive: false + }); + window.removeEventListener("touchend", webViewerTouchEnd, { + passive: false + }); + window.removeEventListener("click", webViewerClick); + window.removeEventListener("keydown", webViewerKeyDown); + window.removeEventListener("keyup", webViewerKeyUp); + window.removeEventListener("resize", _boundEvents.windowResize); + window.removeEventListener("hashchange", _boundEvents.windowHashChange); + window.removeEventListener("beforeprint", _boundEvents.windowBeforePrint); + window.removeEventListener("afterprint", _boundEvents.windowAfterPrint); + window.removeEventListener("updatefromsandbox", _boundEvents.windowUpdateFromSandbox); + mainContainer.removeEventListener("scroll", _boundEvents.mainContainerScroll); + mainContainer.removeEventListener("scrollend", _boundEvents.mainContainerScrollend); + mainContainer.removeEventListener("blur", _boundEvents.mainContainerScrollend); + _boundEvents.removeWindowResolutionChange?.(); + _boundEvents.windowResize = null; + _boundEvents.windowHashChange = null; + _boundEvents.windowBeforePrint = null; + _boundEvents.windowAfterPrint = null; + _boundEvents.windowUpdateFromSandbox = null; + _boundEvents.mainContainerScroll = null; + _boundEvents.mainContainerScrollend = null; }, _accumulateTicks(ticks, prop) { if (this[prop] > 0 && ticks < 0 || this[prop] < 0 && ticks > 0) { @@ -8786,9 +8813,7 @@ function webViewerPageRendered({ } } if (error) { - PDFViewerApplication.l10n.get("pdfjs-rendering-error").then(msg => { - PDFViewerApplication._otherError(msg, error); - }); + PDFViewerApplication._otherError("pdfjs-rendering-error", error); } } function webViewerPageMode({ @@ -8918,9 +8943,6 @@ function webViewerPrint() { function webViewerDownload() { PDFViewerApplication.downloadOrSave(); } -function webViewerOpenInExternalApp() { - PDFViewerApplication.openInExternalApp(); -} function webViewerFirstPage() { PDFViewerApplication.page = 1; } @@ -9509,8 +9531,8 @@ function webViewerReportTelemetry({ -const pdfjsVersion = "4.1.249"; -const pdfjsBuild = "d07f37f44"; +const pdfjsVersion = "4.1.342"; +const pdfjsBuild = "e384df6f1"; const AppConstants = null; window.PDFViewerApplication = PDFViewerApplication; window.PDFViewerApplicationConstants = AppConstants; @@ -9524,8 +9546,7 @@ function getViewerConfiguration() { toolbar: { mainContainer, container: document.getElementById("floatingToolbar"), - download: document.getElementById("download"), - openInApp: document.getElementById("openInApp") + download: document.getElementById("download") }, passwordOverlay: { dialog: document.getElementById("passwordDialog"), diff --git a/toolkit/components/pdfjs/content/web/viewer.css b/toolkit/components/pdfjs/content/web/viewer.css index 238df7ce7f..2999c89f3a 100644 --- a/toolkit/components/pdfjs/content/web/viewer.css +++ b/toolkit/components/pdfjs/content/web/viewer.css @@ -23,7 +23,6 @@ text-size-adjust:none; forced-color-adjust:none; transform-origin:0 0; - z-index:2; caret-color:CanvasText; &.highlighting{ @@ -162,7 +161,6 @@ left:0; pointer-events:none; transform-origin:0 0; - z-index:3; &[data-main-rotation="90"] .norotate{ transform:rotate(270deg) translateX(-100%); @@ -853,6 +851,208 @@ } } +.toggle-button{ + --button-background-color:#f0f0f4; + --button-background-color-hover:#e0e0e6; + --button-background-color-active:#cfcfd8; + --color-accent-primary:#0060df; + --color-accent-primary-hover:#0250bb; + --color-accent-primary-active:#054096; + --border-interactive-color:#8f8f9d; + --border-radius-circle:9999px; + --border-width:1px; + --size-item-small:16px; + --size-item-large:32px; + --color-canvas:white; + + @media (prefers-color-scheme: dark){ + --button-background-color:color-mix(in srgb, currentColor 7%, transparent); + --button-background-color-hover:color-mix( + in srgb, + currentColor 14%, + transparent + ); + --button-background-color-active:color-mix( + in srgb, + currentColor 21%, + transparent + ); + --color-accent-primary:#0df; + --color-accent-primary-hover:#80ebff; + --color-accent-primary-active:#aaf2ff; + --border-interactive-color:#bfbfc9; + --color-canvas:#1c1b22; + } + + @media (forced-colors: active){ + --color-accent-primary:ButtonText; + --color-accent-primary-hover:SelectedItem; + --color-accent-primary-active:SelectedItem; + --border-interactive-color:ButtonText; + --button-background-color:ButtonFace; + --border-interactive-color-hover:SelectedItem; + --border-interactive-color-active:SelectedItem; + --border-interactive-color-disabled:GrayText; + --color-canvas:ButtonText; + } + + --toggle-background-color:var(--button-background-color); + --toggle-background-color-hover:var(--button-background-color-hover); + --toggle-background-color-active:var(--button-background-color-active); + --toggle-background-color-pressed:var(--color-accent-primary); + --toggle-background-color-pressed-hover:var(--color-accent-primary-hover); + --toggle-background-color-pressed-active:var(--color-accent-primary-active); + --toggle-border-color:var(--border-interactive-color); + --toggle-border-color-hover:var(--toggle-border-color); + --toggle-border-color-active:var(--toggle-border-color); + --toggle-border-radius:var(--border-radius-circle); + --toggle-border-width:var(--border-width); + --toggle-height:var(--size-item-small); + --toggle-width:var(--size-item-large); + --toggle-dot-background-color:var(--toggle-border-color); + --toggle-dot-background-color-hover:var(--toggle-dot-background-color); + --toggle-dot-background-color-active:var(--toggle-dot-background-color); + --toggle-dot-background-color-on-pressed:var(--color-canvas); + --toggle-dot-margin:1px; + --toggle-dot-height:calc( + var(--toggle-height) - 2 * var(--toggle-dot-margin) - 2 * + var(--toggle-border-width) + ); + --toggle-dot-width:var(--toggle-dot-height); + --toggle-dot-transform-x:calc( + var(--toggle-width) - 4 * var(--toggle-dot-margin) - var(--toggle-dot-width) + ); + + appearance:none; + padding:0; + margin:0; + border:var(--toggle-border-width) solid var(--toggle-border-color); + height:var(--toggle-height); + width:var(--toggle-width); + border-radius:var(--toggle-border-radius); + background:var(--toggle-background-color); + box-sizing:border-box; + flex-shrink:0; + + &:focus-visible{ + outline:var(--focus-outline); + outline-offset:var(--focus-outline-offset); + } + + &:enabled:hover{ + background:var(--toggle-background-color-hover); + border-color:var(--toggle-border-color); + } + + &:enabled:active{ + background:var(--toggle-background-color-active); + border-color:var(--toggle-border-color); + } + + &[aria-pressed="true"]{ + background:var(--toggle-background-color-pressed); + border-color:transparent; + } + + &[aria-pressed="true"]:enabled:hover{ + background:var(--toggle-background-color-pressed-hover); + border-color:transparent; + } + + &[aria-pressed="true"]:enabled:active{ + background:var(--toggle-background-color-pressed-active); + border-color:transparent; + } + + &::before{ + display:block; + content:""; + background-color:var(--toggle-dot-background-color); + height:var(--toggle-dot-height); + width:var(--toggle-dot-width); + margin:var(--toggle-dot-margin); + border-radius:var(--toggle-border-radius); + translate:0; + } + + &[aria-pressed="true"]::before{ + translate:var(--toggle-dot-transform-x); + background-color:var(--toggle-dot-background-color-on-pressed); + } + + &[aria-pressed="true"]:enabled:hover::before, + &[aria-pressed="true"]:enabled:active::before{ + background-color:var(--toggle-dot-background-color-on-pressed); + } + + &[aria-pressed="true"]:-moz-locale-dir(rtl)::before, + &[aria-pressed="true"]:dir(rtl)::before{ + translate:calc(-1 * var(--toggle-dot-transform-x)); + } + + @media (prefers-reduced-motion: no-preference){ + &::before{ + transition:translate 100ms; + } + } + + @media (prefers-contrast){ + &:enabled:hover{ + border-color:var(--toggle-border-color-hover); + } + + &:enabled:active{ + border-color:var(--toggle-border-color-active); + } + + &[aria-pressed="true"]:enabled{ + border-color:var(--toggle-border-color); + position:relative; + } + + &[aria-pressed="true"]:enabled:hover, + &[aria-pressed="true"]:enabled:hover:active{ + border-color:var(--toggle-border-color-hover); + } + + &[aria-pressed="true"]:enabled:active{ + background-color:var(--toggle-dot-background-color-active); + border-color:var(--toggle-dot-background-color-hover); + } + + &:hover::before, + &:active::before{ + background-color:var(--toggle-dot-background-color-hover); + } + } + + @media (forced-colors){ + --toggle-dot-background-color:var(--color-accent-primary); + --toggle-dot-background-color-hover:var(--color-accent-primary-hover); + --toggle-dot-background-color-active:var(--color-accent-primary-active); + --toggle-dot-background-color-on-pressed:var(--button-background-color); + --toggle-background-color-disabled:var(--button-background-color-disabled); + --toggle-border-color-hover:var(--border-interactive-color-hover); + --toggle-border-color-active:var(--border-interactive-color-active); + --toggle-border-color-disabled:var(--border-interactive-color-disabled); + + &[aria-pressed="true"]:enabled::after{ + border:1px solid var(--button-background-color); + content:""; + position:absolute; + height:var(--toggle-height); + width:var(--toggle-width); + display:block; + border-radius:var(--toggle-border-radius); + inset:-2px; + } + + &[aria-pressed="true"]:enabled:active::after{ + border-color:var(--toggle-border-color-active); + } + } +} + :root{ --outline-width:2px; --outline-color:#0060df; @@ -878,6 +1078,19 @@ --editorHighlight-editing-cursor:url(images/cursor-editorTextHighlight.svg) 24 24, text; --editorFreeHighlight-editing-cursor:url(images/cursor-editorFreeHighlight.svg) 1 18, pointer; } +.visuallyHidden{ + position:absolute; + top:0; + left:0; + border:0; + margin:0; + padding:0; + width:0; + height:0; + overflow:hidden; + white-space:nowrap; + font-size:0; +} .textLayer.highlighting{ cursor:var(--editorFreeHighlight-editing-cursor); @@ -926,7 +1139,6 @@ font-size:calc(100px * var(--scale-factor)); transform-origin:0 0; cursor:auto; - z-index:4; } .annotationEditorLayer.waiting{ @@ -995,10 +1207,12 @@ } .annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor){ + :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor), +.textLayer{ .editToolbar{ --editor-toolbar-delete-image:url(images/editor-toolbar-delete.svg); --editor-toolbar-bg-color:#f0f0f4; + --editor-toolbar-highlight-image:url(images/toolbarButton-editorHighlight.svg); --editor-toolbar-fg-color:#2e2e56; --editor-toolbar-border-color:#8f8f9d; --editor-toolbar-hover-border-color:var(--editor-toolbar-border-color); @@ -1084,6 +1298,25 @@ margin-inline:2px; } + .highlightButton{ + width:var(--editor-toolbar-height); + + &::before{ + content:""; + mask-image:var(--editor-toolbar-highlight-image); + mask-repeat:no-repeat; + mask-position:center; + display:inline-block; + background-color:var(--editor-toolbar-fg-color); + width:100%; + height:100%; + } + + &:hover::before{ + background-color:var(--editor-toolbar-hover-fg-color); + } + } + .delete{ width:var(--editor-toolbar-height); @@ -2003,7 +2236,12 @@ --example-color:CanvasText; } - &::before{ + :is(& > .editorParamsSlider[disabled]){ + opacity:0.4; + } + + &::before, + &::after{ content:""; width:8px; aspect-ratio:1; @@ -2011,20 +2249,46 @@ border-radius:100%; background-color:var(--example-color); } + &::after{ + width:24px; + } .editorParamsSlider{ width:unset; height:14px; } + } + } - &::after{ - content:""; - width:24px; - aspect-ratio:1; - display:block; - border-radius:100%; - background-color:var(--example-color); + #editorHighlightVisibility{ + display:flex; + flex-direction:column; + align-items:flex-start; + gap:8px; + align-self:stretch; + + .divider{ + --divider-color:#d7d7db; + + @media (prefers-color-scheme: dark){ + --divider-color:#8f8f9d; + } + + @media screen and (forced-colors: active){ + --divider-color:CanvasText; } + + margin-block:4px; + width:100%; + height:1px; + background-color:var(--divider-color); + } + + .toggler{ + display:flex; + justify-content:space-between; + align-items:center; + align-self:stretch; } } } @@ -2084,7 +2348,6 @@ overflow:hidden; width:100%; height:100%; - z-index:1; } .pdfViewer .page{ @@ -2655,6 +2918,7 @@ body{ font-style:normal; } .loadingInput:has(> &[data-status="pending"])::after{ + display:block; visibility:visible; } &[data-status="notFound"]{ @@ -3249,6 +3513,7 @@ a:is(.toolbarButton, .secondaryToolbarButton)[href="#"]{ transition-property:none; .loadingInput:has(> &.loading)::after{ + display: block; visibility:visible; transition-property:visibility; @@ -3260,6 +3525,7 @@ a:is(.toolbarButton, .secondaryToolbarButton)[href="#"]{ &::after{ position:absolute; visibility:hidden; + display: none; top:calc(50% - 8px); width:16px; height:16px; diff --git a/toolkit/components/pdfjs/content/web/viewer.html b/toolkit/components/pdfjs/content/web/viewer.html index 59438b64b4..941e17b58d 100644 --- a/toolkit/components/pdfjs/content/web/viewer.html +++ b/toolkit/components/pdfjs/content/web/viewer.html @@ -127,6 +127,13 @@ See https://github.com/adobe-type-tools/cmap-resources <input type="range" id="editorFreeHighlightThickness" class="editorParamsSlider" data-l10n-id="pdfjs-editor-free-highlight-thickness-title" value="12" min="8" max="24" step="1" tabindex="101"> </div> </div> + <div id="editorHighlightVisibility"> + <div class="divider"></div> + <div class="toggler"> + <label for="editorHighlightShowAll" class="editorParamsLabel" data-l10n-id="pdfjs-editor-highlight-show-all-button-label">Show all</label> + <button id="editorHighlightShowAll" class="toggle-button" data-l10n-id="pdfjs-editor-highlight-show-all-button" aria-pressed="true" tabindex="102"></button> + </div> + </div> </div> </div> @@ -134,11 +141,11 @@ See https://github.com/adobe-type-tools/cmap-resources <div class="editorParamsToolbarContainer"> <div class="editorParamsSetter"> <label for="editorFreeTextColor" class="editorParamsLabel" data-l10n-id="pdfjs-editor-free-text-color-input">Color</label> - <input type="color" id="editorFreeTextColor" class="editorParamsColor" tabindex="102"> + <input type="color" id="editorFreeTextColor" class="editorParamsColor" tabindex="103"> </div> <div class="editorParamsSetter"> <label for="editorFreeTextFontSize" class="editorParamsLabel" data-l10n-id="pdfjs-editor-free-text-size-input">Size</label> - <input type="range" id="editorFreeTextFontSize" class="editorParamsSlider" value="10" min="5" max="100" step="1" tabindex="103"> + <input type="range" id="editorFreeTextFontSize" class="editorParamsSlider" value="10" min="5" max="100" step="1" tabindex="104"> </div> </div> </div> @@ -147,22 +154,22 @@ See https://github.com/adobe-type-tools/cmap-resources <div class="editorParamsToolbarContainer"> <div class="editorParamsSetter"> <label for="editorInkColor" class="editorParamsLabel" data-l10n-id="pdfjs-editor-ink-color-input">Color</label> - <input type="color" id="editorInkColor" class="editorParamsColor" tabindex="104"> + <input type="color" id="editorInkColor" class="editorParamsColor" tabindex="105"> </div> <div class="editorParamsSetter"> <label for="editorInkThickness" class="editorParamsLabel" data-l10n-id="pdfjs-editor-ink-thickness-input">Thickness</label> - <input type="range" id="editorInkThickness" class="editorParamsSlider" value="1" min="1" max="20" step="1" tabindex="105"> + <input type="range" id="editorInkThickness" class="editorParamsSlider" value="1" min="1" max="20" step="1" tabindex="106"> </div> <div class="editorParamsSetter"> <label for="editorInkOpacity" class="editorParamsLabel" data-l10n-id="pdfjs-editor-ink-opacity-input">Opacity</label> - <input type="range" id="editorInkOpacity" class="editorParamsSlider" value="100" min="1" max="100" step="1" tabindex="106"> + <input type="range" id="editorInkOpacity" class="editorParamsSlider" value="100" min="1" max="100" step="1" tabindex="107"> </div> </div> </div> <div class="editorParamsToolbar hidden doorHangerRight" id="editorStampParamsToolbar"> <div class="editorParamsToolbarContainer"> - <button id="editorStampAddImage" class="secondaryToolbarButton" title="Add image" tabindex="107" data-l10n-id="pdfjs-editor-stamp-add-image-button"> + <button id="editorStampAddImage" class="secondaryToolbarButton" title="Add image" tabindex="108" data-l10n-id="pdfjs-editor-stamp-add-image-button"> <span class="editorParamsLabel" data-l10n-id="pdfjs-editor-stamp-add-image-button-label">Add image</span> </button> </div> diff --git a/toolkit/components/pdfjs/content/web/viewer.mjs b/toolkit/components/pdfjs/content/web/viewer.mjs index a047a9f7c1..778ce57e1a 100644 --- a/toolkit/components/pdfjs/content/web/viewer.mjs +++ b/toolkit/components/pdfjs/content/web/viewer.mjs @@ -606,6 +606,10 @@ const defaultOptions = { value: false, kind: OptionKind.VIEWER + OptionKind.PREFERENCE }, + enableHighlightFloatingButton: { + value: false, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE + }, enableML: { value: false, kind: OptionKind.VIEWER + OptionKind.PREFERENCE @@ -651,7 +655,7 @@ const defaultOptions = { kind: OptionKind.VIEWER }, maxCanvasPixels: { - value: 16777216, + value: 2 ** 25, kind: OptionKind.VIEWER }, forcePageColors: { @@ -768,28 +772,20 @@ class AppOptions { constructor() { throw new Error("Cannot initialize AppOptions."); } + static getCompat(name) { + return compatibilityParams[name] ?? undefined; + } static get(name) { - const userOption = userOptions[name]; - if (userOption !== undefined) { - return userOption; - } - const defaultOption = defaultOptions[name]; - if (defaultOption !== undefined) { - return compatibilityParams[name] ?? defaultOption.value; - } - return undefined; + return userOptions[name] ?? compatibilityParams[name] ?? defaultOptions[name]?.value ?? undefined; } - static getAll(kind = null) { + static getAll(kind = null, defaultOnly = false) { const options = Object.create(null); for (const name in defaultOptions) { const defaultOption = defaultOptions[name]; - if (kind) { - if (!(kind & defaultOption.kind)) { - continue; - } + if (kind && !(kind & defaultOption.kind)) { + continue; } - const userOption = userOptions[name]; - options[name] = userOption !== undefined ? userOption : compatibilityParams[name] ?? defaultOption.value; + options[name] = defaultOnly ? defaultOption.value : userOptions[name] ?? compatibilityParams[name] ?? defaultOption.value; } return options; } @@ -1112,30 +1108,7 @@ class PDFLinkService { if (pdfDocument !== this.pdfDocument) { return; } - let operator; - for (const elem of action.state) { - switch (elem) { - case "ON": - case "OFF": - case "Toggle": - operator = elem; - continue; - } - switch (operator) { - case "ON": - optionalContentConfig.setVisibility(elem, true); - break; - case "OFF": - optionalContentConfig.setVisibility(elem, false); - break; - case "Toggle": - const group = optionalContentConfig.getGroup(elem); - if (group) { - optionalContentConfig.setVisibility(elem, !group.visible); - } - break; - } - } + optionalContentConfig.setOCGState(action); this.pdfViewer.optionalContentConfigPromise = Promise.resolve(optionalContentConfig); } cachePageRef(pageNum, pageRef) { @@ -1423,7 +1396,7 @@ class BaseExternalServices { } updateFindControlState(data) {} updateFindMatchesCount(data) {} - initPassiveLoading(callbacks) {} + initPassiveLoading() {} reportTelemetry(data) {} async createL10n() { throw new Error("Not implemented: createL10n"); @@ -1440,6 +1413,16 @@ class BaseExternalServices { ;// CONCATENATED MODULE: ./web/preferences.js class BasePreferences { + #browserDefaults = Object.freeze({ + canvasMaxAreaInBytes: -1, + isInAutomation: false, + supportsCaretBrowsingMode: false, + supportsDocumentFonts: true, + supportsIntegratedFind: false, + supportsMouseWheelZoomCtrlKey: true, + supportsMouseWheelZoomMetaKey: true, + supportsPinchToZoom: true + }); #defaults = Object.freeze({ annotationEditorMode: 0, annotationMode: 2, @@ -1448,6 +1431,7 @@ class BasePreferences { defaultZoomValue: "", disablePageLabels: false, enableHighlightEditor: false, + enableHighlightFloatingButton: false, enableML: false, enablePermissions: false, enablePrintAutoRotate: true, @@ -1482,26 +1466,19 @@ class BasePreferences { browserPrefs, prefs }) => { - const BROWSER_PREFS = { - canvasMaxAreaInBytes: -1, - isInAutomation: false, - supportsCaretBrowsingMode: false, - supportsDocumentFonts: true, - supportsIntegratedFind: false, - supportsMouseWheelZoomCtrlKey: true, - supportsMouseWheelZoomMetaKey: true, - supportsPinchToZoom: true - }; const options = Object.create(null); - for (const [name, defaultVal] of Object.entries(BROWSER_PREFS)) { + for (const [name, val] of Object.entries(this.#browserDefaults)) { const prefVal = browserPrefs?.[name]; - options[name] = typeof prefVal === typeof defaultVal ? prefVal : defaultVal; + options[name] = typeof prefVal === typeof val ? prefVal : val; } - for (const [name, defaultVal] of Object.entries(this.#defaults)) { + for (const [name, val] of Object.entries(this.#defaults)) { const prefVal = prefs?.[name]; - options[name] = this.#prefs[name] = typeof prefVal === typeof defaultVal ? prefVal : defaultVal; + options[name] = this.#prefs[name] = typeof prefVal === typeof val ? prefVal : val; } AppOptions.setAll(options, true); + window.addEventListener("updatedPreference", evt => { + this.#updatePref(evt.detail); + }); }); } async _writeToStorage(prefObj) { @@ -1510,6 +1487,24 @@ class BasePreferences { async _readFromStorage(prefObj) { throw new Error("Not implemented: _readFromStorage"); } + #updatePref({ + name, + value + }) { + if (name in this.#browserDefaults) { + if (typeof value !== typeof this.#browserDefaults[name]) { + return; + } + } else if (name in this.#defaults) { + if (typeof value !== typeof this.#defaults[name]) { + return; + } + this.#prefs[name] = value; + } else { + return; + } + AppOptions.set(name, value); + } async reset() { throw new Error("Please use `about:config` to change preferences."); } @@ -1517,12 +1512,7 @@ class BasePreferences { throw new Error("Please use `about:config` to change preferences."); } async get(name) { - await this.#initializedPromise; - const defaultValue = this.#defaults[name]; - if (defaultValue === undefined) { - throw new Error(`Get preference: "${name}" is undefined.`); - } - return this.#prefs[name] ?? defaultValue; + throw new Error("Not implemented: get"); } get initializedPromise() { return this.#initializedPromise; @@ -1824,7 +1814,7 @@ class ExternalServices extends BaseExternalServices { updateFindMatchesCount(data) { FirefoxCom.request("updateFindMatchesCount", data); } - initPassiveLoading(callbacks) { + initPassiveLoading() { let pdfDataRangeTransport; window.addEventListener("message", function windowMessage(e) { if (e.source !== null) { @@ -1838,11 +1828,13 @@ class ExternalServices extends BaseExternalServices { switch (args.pdfjsLoadAction) { case "supportsRangedLoading": if (args.done && !args.data) { - callbacks.onError(); + viewerApp._documentError(null); break; } pdfDataRangeTransport = new FirefoxComDataRangeTransport(args.length, args.data, args.done, args.filename); - callbacks.onOpenWithTransport(pdfDataRangeTransport); + viewerApp.open({ + range: pdfDataRangeTransport + }); break; case "range": pdfDataRangeTransport.onDataRange(args.begin, args.chunk); @@ -1858,21 +1850,26 @@ class ExternalServices extends BaseExternalServices { pdfDataRangeTransport?.onDataProgressiveDone(); break; case "progress": - callbacks.onProgress(args.loaded, args.total); + viewerApp.progress(args.loaded / args.total); break; case "complete": if (!args.data) { - callbacks.onError(args.errorCode); + viewerApp._documentError(null, { + message: args.errorCode + }); break; } - callbacks.onOpenWithData(args.data, args.filename); + viewerApp.open({ + data: args.data, + filename: args.filename + }); break; } }); FirefoxCom.request("initPassiveLoading", null); } reportTelemetry(data) { - FirefoxCom.request("reportTelemetry", JSON.stringify(data)); + FirefoxCom.request("reportTelemetry", data); } updateEditorStates(data) { FirefoxCom.request("updateEditorStates", data); @@ -2150,7 +2147,8 @@ class AnnotationEditorParams { editorInkThickness, editorInkOpacity, editorStampAddImage, - editorFreeHighlightThickness + editorFreeHighlightThickness, + editorHighlightShowAll }) { const dispatchEvent = (typeStr, value) => { this.eventBus.dispatch("switchannotationeditorparams", { @@ -2180,6 +2178,11 @@ class AnnotationEditorParams { editorFreeHighlightThickness.addEventListener("input", function () { dispatchEvent("HIGHLIGHT_THICKNESS", this.valueAsNumber); }); + editorHighlightShowAll.addEventListener("click", function () { + const checked = this.getAttribute("aria-pressed") === "true"; + this.setAttribute("aria-pressed", !checked); + dispatchEvent("HIGHLIGHT_SHOW_ALL", !checked); + }); this.eventBus._on("annotationeditorparamschanged", evt => { for (const [type, value] of evt.details) { switch (type) { @@ -2204,6 +2207,9 @@ class AnnotationEditorParams { case AnnotationEditorParamsType.HIGHLIGHT_FREE: editorFreeHighlightThickness.disabled = !value; break; + case AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL: + editorHighlightShowAll.setAttribute("aria-pressed", value); + break; } } }); @@ -4690,7 +4696,9 @@ class PDFLayerViewer extends BaseTreeViewer { return; } const pdfDocument = this._pdfDocument; - const optionalContentConfig = await (promise || pdfDocument.getOptionalContentConfig()); + const optionalContentConfig = await (promise || pdfDocument.getOptionalContentConfig({ + intent: "display" + })); if (pdfDocument !== this._pdfDocument) { return; } @@ -5429,14 +5437,15 @@ class FirefoxPrintService { pagesOverview, printContainer, printResolution, - optionalContentConfigPromise = null, printAnnotationStoragePromise = null }) { this.pdfDocument = pdfDocument; this.pagesOverview = pagesOverview; this.printContainer = printContainer; this._printResolution = printResolution || 150; - this._optionalContentConfigPromise = optionalContentConfigPromise || pdfDocument.getOptionalContentConfig(); + this._optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({ + intent: "print" + }); this._printAnnotationStoragePromise = printAnnotationStoragePromise || Promise.resolve(); } layout() { @@ -6680,7 +6689,9 @@ class PDFThumbnailViewer { return; } const firstPagePromise = pdfDocument.getPage(1); - const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig(); + const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({ + intent: "display" + }); firstPagePromise.then(firstPdfPage => { const pagesCount = pdfDocument.numPages; const viewport = firstPdfPage.getViewport({ @@ -6770,10 +6781,10 @@ class PDFThumbnailViewer { class AnnotationEditorLayerBuilder { #annotationLayer = null; #drawLayer = null; + #onAppend = null; #textLayer = null; #uiManager; constructor(options) { - this.pageDiv = options.pageDiv; this.pdfPage = options.pdfPage; this.accessibilityManager = options.accessibilityManager; this.l10n = options.l10n; @@ -6784,6 +6795,7 @@ class AnnotationEditorLayerBuilder { this.#annotationLayer = options.annotationLayer || null; this.#textLayer = options.textLayer || null; this.#drawLayer = options.drawLayer || null; + this.#onAppend = options.onAppend || null; } async render(viewport, intent = "display") { if (intent !== "display") { @@ -6804,10 +6816,9 @@ class AnnotationEditorLayerBuilder { } const div = this.div = document.createElement("div"); div.className = "annotationEditorLayer"; - div.tabIndex = 0; div.hidden = true; div.dir = this.#uiManager.direction; - this.pageDiv.append(div); + this.#onAppend?.(div); this.annotationEditorLayer = new AnnotationEditorLayer({ uiManager: this.#uiManager, div, @@ -6833,9 +6844,7 @@ class AnnotationEditorLayerBuilder { if (!this.div) { return; } - this.pageDiv = null; this.annotationEditorLayer.destroy(); - this.div.remove(); } hide() { if (!this.div) { @@ -6844,7 +6853,7 @@ class AnnotationEditorLayerBuilder { this.div.hidden = true; } show() { - if (!this.div || this.annotationEditorLayer.isEmpty) { + if (!this.div || this.annotationEditorLayer.isInvisible) { return; } this.div.hidden = false; @@ -6855,9 +6864,9 @@ class AnnotationEditorLayerBuilder { class AnnotationLayerBuilder { + #onAppend = null; #onPresentationModeChanged = null; constructor({ - pageDiv, pdfPage, linkService, downloadManager, @@ -6868,9 +6877,9 @@ class AnnotationLayerBuilder { hasJSActionsPromise = null, fieldObjectsPromise = null, annotationCanvasMap = null, - accessibilityManager = null + accessibilityManager = null, + onAppend = null }) { - this.pageDiv = pageDiv; this.pdfPage = pdfPage; this.linkService = linkService; this.downloadManager = downloadManager; @@ -6882,6 +6891,7 @@ class AnnotationLayerBuilder { this._fieldObjectsPromise = fieldObjectsPromise || Promise.resolve(null); this._annotationCanvasMap = annotationCanvasMap; this._accessibilityManager = accessibilityManager; + this.#onAppend = onAppend; this.annotationLayer = null; this.div = null; this._cancelled = false; @@ -6907,7 +6917,7 @@ class AnnotationLayerBuilder { } const div = this.div = document.createElement("div"); div.className = "annotationLayer"; - this.pageDiv.append(div); + this.#onAppend?.(div); if (annotations.length === 0) { this.hide(); return; @@ -7501,13 +7511,15 @@ class TextHighlighter { class TextLayerBuilder { #enablePermissions = false; + #onAppend = null; #rotation = 0; #scale = 0; #textContentSource = null; constructor({ highlighter = null, accessibilityManager = null, - enablePermissions = false + enablePermissions = false, + onAppend = null }) { this.textContentItemsStr = []; this.renderingDone = false; @@ -7517,8 +7529,9 @@ class TextLayerBuilder { this.highlighter = highlighter; this.accessibilityManager = accessibilityManager; this.#enablePermissions = enablePermissions === true; - this.onAppend = null; + this.#onAppend = onAppend; this.div = document.createElement("div"); + this.div.tabIndex = 0; this.div.className = "textLayer"; } #finishRendering() { @@ -7573,7 +7586,7 @@ class TextLayerBuilder { this.#finishRendering(); this.#scale = scale; this.#rotation = rotation; - this.onAppend(this.div); + this.#onAppend?.(this.div); this.highlighter?.enable(); this.accessibilityManager?.enable(); } @@ -7647,8 +7660,8 @@ class TextLayerBuilder { -const MAX_CANVAS_PIXELS = compatibilityParams.maxCanvasPixels || 16777216; const DEFAULT_LAYER_PROPERTIES = null; +const LAYERS_ORDER = new Map([["canvasWrapper", 0], ["textLayer", 1], ["annotationLayer", 2], ["annotationEditorLayer", 3], ["xfaLayer", 3]]); class PDFPageView { #annotationMode = AnnotationMode.ENABLE_FORMS; #hasRestrictedScaling = false; @@ -7664,6 +7677,7 @@ class PDFPageView { regularAnnotations: true }; #viewportMap = new WeakMap(); + #layers = [null, null, null, null]; constructor(options) { const container = options.container; const defaultViewport = options.defaultViewport; @@ -7680,7 +7694,7 @@ class PDFPageView { this.#textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE; this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS; this.imageResourcesPath = options.imageResourcesPath || ""; - this.maxCanvasPixels = options.maxCanvasPixels ?? MAX_CANVAS_PIXELS; + this.maxCanvasPixels = options.maxCanvasPixels ?? (AppOptions.getCompat("maxCanvasPixels") || 2 ** 25); this.pageColors = options.pageColors || null; this.eventBus = options.eventBus; this.renderingQueue = options.renderingQueue; @@ -7707,6 +7721,23 @@ class PDFPageView { this.#setDimensions(); container?.append(div); } + #addLayer(div, name) { + const pos = LAYERS_ORDER.get(name); + const oldDiv = this.#layers[pos]; + this.#layers[pos] = div; + if (oldDiv) { + oldDiv.replaceWith(div); + return; + } + for (let i = pos - 1; i >= 0; i--) { + const layer = this.#layers[i]; + if (layer) { + layer.after(div); + return; + } + } + this.div.prepend(div); + } get renderingState() { return this.#renderingState; } @@ -7820,7 +7851,7 @@ class PDFPageView { } finally { if (this.xfaLayer?.div) { this.l10n.pause(); - this.div.append(this.xfaLayer.div); + this.#addLayer(this.xfaLayer.div, "xfaLayer"); this.l10n.resume(); } this.eventBus.dispatch("xfalayerrendered", { @@ -7932,6 +7963,10 @@ class PDFPageView { continue; } node.remove(); + const layerIndex = this.#layers.indexOf(node); + if (layerIndex >= 0) { + this.#layers[layerIndex] = null; + } } div.removeAttribute("data-loaded"); if (annotationLayerNode) { @@ -8191,19 +8226,20 @@ class PDFPageView { this.renderingState = RenderingStates.RUNNING; const canvasWrapper = document.createElement("div"); canvasWrapper.classList.add("canvasWrapper"); - div.append(canvasWrapper); + canvasWrapper.setAttribute("aria-hidden", true); + this.#addLayer(canvasWrapper, "canvasWrapper"); if (!this.textLayer && this.#textLayerMode !== TextLayerMode.DISABLE && !pdfPage.isPureXfa) { this._accessibilityManager ||= new TextAccessibilityManager(); this.textLayer = new TextLayerBuilder({ highlighter: this._textHighlighter, accessibilityManager: this._accessibilityManager, - enablePermissions: this.#textLayerMode === TextLayerMode.ENABLE_PERMISSIONS + enablePermissions: this.#textLayerMode === TextLayerMode.ENABLE_PERMISSIONS, + onAppend: textLayerDiv => { + this.l10n.pause(); + this.#addLayer(textLayerDiv, "textLayer"); + this.l10n.resume(); + } }); - this.textLayer.onAppend = textLayerDiv => { - this.l10n.pause(); - this.div.append(textLayerDiv); - this.l10n.resume(); - }; } if (!this.annotationLayer && this.#annotationMode !== AnnotationMode.DISABLE) { const { @@ -8216,7 +8252,6 @@ class PDFPageView { } = this.#layerProperties; this._annotationCanvasMap ||= new Map(); this.annotationLayer = new AnnotationLayerBuilder({ - pageDiv: div, pdfPage, annotationStorage, imageResourcesPath: this.imageResourcesPath, @@ -8227,7 +8262,10 @@ class PDFPageView { hasJSActionsPromise, fieldObjectsPromise, annotationCanvasMap: this._annotationCanvasMap, - accessibilityManager: this._accessibilityManager + accessibilityManager: this._accessibilityManager, + onAppend: annotationLayerDiv => { + this.#addLayer(annotationLayerDiv, "annotationLayer"); + } }); } const renderContinueCallback = cont => { @@ -8316,13 +8354,15 @@ class PDFPageView { if (!this.annotationEditorLayer) { this.annotationEditorLayer = new AnnotationEditorLayerBuilder({ uiManager: annotationEditorUIManager, - pageDiv: div, pdfPage, l10n, accessibilityManager: this._accessibilityManager, annotationLayer: this.annotationLayer?.annotationLayer, textLayer: this.textLayer, - drawLayer: this.drawLayer.getDrawLayer() + drawLayer: this.drawLayer.getDrawLayer(), + onAppend: annotationEditorLayerDiv => { + this.#addLayer(annotationEditorLayerDiv, "annotationEditorLayer"); + } }); } this.#renderAnnotationEditorLayer(); @@ -8447,6 +8487,7 @@ class PDFViewer { #annotationMode = AnnotationMode.ENABLE_FORMS; #containerTopLeft = null; #copyCallbackBound = null; + #enableHighlightFloatingButton = false; #enablePermissions = false; #mlManager = null; #getAllTextInProgress = false; @@ -8459,7 +8500,7 @@ class PDFViewer { #scaleTimeoutId = null; #textLayerMode = TextLayerMode.ENABLE; constructor(options) { - const viewerVersion = "4.1.249"; + const viewerVersion = "4.1.342"; if (version !== viewerVersion) { throw new Error(`The API version "${version}" does not match the Viewer version "${viewerVersion}".`); } @@ -8479,6 +8520,7 @@ class PDFViewer { this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS; this.#annotationEditorMode = options.annotationEditorMode ?? AnnotationEditorType.NONE; this.#annotationEditorHighlightColors = options.annotationEditorHighlightColors || null; + this.#enableHighlightFloatingButton = options.enableHighlightFloatingButton === true; this.imageResourcesPath = options.imageResourcesPath || ""; this.enablePrintAutoRotate = options.enablePrintAutoRotate || false; this.maxCanvasPixels = options.maxCanvasPixels; @@ -8789,7 +8831,9 @@ class PDFViewer { } const pagesCount = pdfDocument.numPages; const firstPagePromise = pdfDocument.getPage(1); - const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig(); + const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({ + intent: "display" + }); const permissionsPromise = this.#enablePermissions ? pdfDocument.getPermissions() : Promise.resolve(); if (pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE) { console.warn("Forcing PAGE-scrolling for performance reasons, given the length of the document."); @@ -8845,7 +8889,7 @@ class PDFViewer { if (pdfDocument.isPureXfa) { console.warn("Warning: XFA-editing is not implemented."); } else if (isValidAnnotationEditorMode(mode)) { - this.#annotationEditorUIManager = new AnnotationEditorUIManager(this.container, this.viewer, this.#altTextManager, this.eventBus, pdfDocument, this.pageColors, this.#annotationEditorHighlightColors, this.#mlManager); + this.#annotationEditorUIManager = new AnnotationEditorUIManager(this.container, this.viewer, this.#altTextManager, this.eventBus, pdfDocument, this.pageColors, this.#annotationEditorHighlightColors, this.#enableHighlightFloatingButton, this.#mlManager); this.eventBus.dispatch("annotationeditoruimanager", { source: this, uiManager: this.#annotationEditorUIManager @@ -9506,7 +9550,9 @@ class PDFViewer { } if (!this._optionalContentConfigPromise) { console.error("optionalContentConfigPromise: Not initialized yet."); - return this.pdfDocument.getOptionalContentConfig(); + return this.pdfDocument.getOptionalContentConfig({ + intent: "display" + }); } return this._optionalContentConfigPromise; } @@ -10694,6 +10740,7 @@ const PDFViewerApplication = { annotationMode: AppOptions.get("annotationMode"), annotationEditorMode, annotationEditorHighlightColors: AppOptions.get("highlightEditorColors"), + enableHighlightFloatingButton: AppOptions.get("enableHighlightFloatingButton"), imageResourcesPath: AppOptions.get("imageResourcesPath"), enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"), maxCanvasPixels: AppOptions.get("maxCanvasPixels"), @@ -10833,40 +10880,8 @@ const PDFViewerApplication = { if (this.supportsIntegratedFind) { appConfig.toolbar?.viewFind?.classList.add("hidden"); } - this.initPassiveLoading(file); - const { - mainContainer - } = appConfig; - ({ - scrollTop: this._lastScrollTop, - scrollLeft: this._lastScrollLeft - } = mainContainer); - const scroll = () => { - if (this._lastScrollTop === mainContainer.scrollTop && this._lastScrollLeft === mainContainer.scrollLeft) { - return; - } - mainContainer.removeEventListener("scroll", scroll, { - passive: true - }); - this._isScrolling = true; - const scrollend = () => { - ({ - scrollTop: this._lastScrollTop, - scrollLeft: this._lastScrollLeft - } = mainContainer); - this._isScrolling = false; - mainContainer.addEventListener("scroll", scroll, { - passive: true - }); - mainContainer.removeEventListener("scrollend", scrollend); - mainContainer.removeEventListener("blur", scrollend); - }; - mainContainer.addEventListener("scrollend", scrollend); - mainContainer.addEventListener("blur", scrollend); - }; - mainContainer.addEventListener("scroll", scroll, { - passive: true - }); + this.setTitleUsingUrl(file, file); + this.externalServices.initPassiveLoading(); }, get externalServices() { return shadow(this, "externalServices", new ExternalServices()); @@ -10939,45 +10954,12 @@ const PDFViewerApplication = { return shadow(this, "supportsMouseWheelZoomMetaKey", AppOptions.get("supportsMouseWheelZoomMetaKey")); }, get supportsCaretBrowsingMode() { - return shadow(this, "supportsCaretBrowsingMode", AppOptions.get("supportsCaretBrowsingMode")); + return AppOptions.get("supportsCaretBrowsingMode"); }, moveCaret(isUp, select) { this._caretBrowsing ||= new CaretBrowsingMode(this.appConfig.mainContainer, this.appConfig.viewerContainer, this.appConfig.toolbar?.container); this._caretBrowsing.moveCaret(isUp, select); }, - initPassiveLoading(file) { - this.setTitleUsingUrl(file, file); - this.externalServices.initPassiveLoading({ - onOpenWithTransport: range => { - this.open({ - range - }); - }, - onOpenWithData: (data, contentDispositionFilename) => { - if (isPdfFile(contentDispositionFilename)) { - this._contentDispositionFilename = contentDispositionFilename; - } - this.open({ - data - }); - }, - onOpenWithURL: (url, length, originalUrl) => { - this.open({ - url, - length, - originalUrl - }); - }, - onError: err => { - this.l10n.get("pdfjs-loading-error").then(msg => { - this._documentError(msg, err); - }); - }, - onProgress: (loaded, total) => { - this.progress(loaded / total); - } - }); - }, setTitleUsingUrl(url = "", downloadUrl = null) { this.url = url; this.baseUrl = url.split("#", 1)[0]; @@ -11065,6 +11047,9 @@ const PDFViewerApplication = { } const workerParams = AppOptions.getAll(OptionKind.WORKER); Object.assign(GlobalWorkerOptions, workerParams); + if (args.data && isPdfFile(args.filename)) { + this._contentDispositionFilename = args.filename; + } AppOptions.set("docBaseUrl", this.baseUrl); const apiParams = AppOptions.getAll(OptionKind.API); const loadingTask = getDocument({ @@ -11100,10 +11085,9 @@ const PDFViewerApplication = { } else if (reason instanceof UnexpectedResponseException) { key = "pdfjs-unexpected-response-error"; } - return this.l10n.get(key).then(msg => { - this._documentError(msg, { - message: reason?.message - }); + return this._documentError(key, { + message: reason.message + }).then(() => { throw reason; }); }); @@ -11167,21 +11151,17 @@ const PDFViewerApplication = { this.download(options); } }, - openInExternalApp() { - this.downloadOrSave({ - openInExternalApp: true - }); - }, - _documentError(message, moreInfo = null) { + async _documentError(key, moreInfo = null) { this._unblockDocumentLoadEvent(); - this._otherError(message, moreInfo); + const message = await this._otherError(key || "pdfjs-loading-error", moreInfo); this.eventBus.dispatch("documenterror", { source: this, message, reason: moreInfo?.message ?? null }); }, - _otherError(message, moreInfo = null) { + async _otherError(key, moreInfo = null) { + const message = await this.l10n.get(key); const moreInfoText = [`PDF.js v${version || "?"} (build: ${build || "?"})`]; if (moreInfo) { moreInfoText.push(`Message: ${moreInfo.message}`); @@ -11197,6 +11177,7 @@ const PDFViewerApplication = { } } console.error(`${message}\n\n${moreInfoText.join("\n")}`); + return message; }, progress(level) { if (!this.loadingBar || this.downloadComplete) { @@ -11321,10 +11302,8 @@ const PDFViewerApplication = { this._unblockDocumentLoadEvent(); this._initializeAutoPrint(pdfDocument, openActionPromise); }, reason => { - this.l10n.get("pdfjs-loading-error").then(msg => { - this._documentError(msg, { - message: reason?.message - }); + this._documentError("pdfjs-loading-error", { + message: reason.message }); }); onePageRendered.then(data => { @@ -11603,9 +11582,7 @@ const PDFViewerApplication = { return; } if (!this.supportsPrinting) { - this.l10n.get("pdfjs-printing-not-supported").then(msg => { - this._otherError(msg); - }); + this._otherError("pdfjs-printing-not-supported"); return; } if (!this.pdfViewer.pageViewsReady) { @@ -11619,7 +11596,6 @@ const PDFViewerApplication = { pagesOverview: this.pdfViewer.getPagesOverview(), printContainer: this.appConfig.printContainer, printResolution: AppOptions.get("printResolution"), - optionalContentConfigPromise: this.pdfViewer.optionalContentConfigPromise, printAnnotationStoragePromise: this._printAnnotationStoragePromise }); this.forceRendering(); @@ -11688,7 +11664,6 @@ const PDFViewerApplication = { eventBus._on("switchannotationeditorparams", webViewerSwitchAnnotationEditorParams); eventBus._on("print", webViewerPrint); eventBus._on("download", webViewerDownload); - eventBus._on("openinexternalapp", webViewerOpenInExternalApp); eventBus._on("firstpage", webViewerFirstPage); eventBus._on("lastpage", webViewerLastPage); eventBus._on("nextpage", webViewerNextPage); @@ -11720,7 +11695,10 @@ const PDFViewerApplication = { bindWindowEvents() { const { eventBus, - _boundEvents + _boundEvents, + appConfig: { + mainContainer + } } = this; function addWindowResolutionChange(evt = null) { if (evt) { @@ -11780,12 +11758,79 @@ const PDFViewerApplication = { window.addEventListener("beforeprint", _boundEvents.windowBeforePrint); window.addEventListener("afterprint", _boundEvents.windowAfterPrint); window.addEventListener("updatefromsandbox", _boundEvents.windowUpdateFromSandbox); + ({ + scrollTop: this._lastScrollTop, + scrollLeft: this._lastScrollLeft + } = mainContainer); + const scrollend = _boundEvents.mainContainerScrollend = () => { + ({ + scrollTop: this._lastScrollTop, + scrollLeft: this._lastScrollLeft + } = mainContainer); + this._isScrolling = false; + mainContainer.addEventListener("scroll", scroll, { + passive: true + }); + mainContainer.removeEventListener("scrollend", scrollend); + mainContainer.removeEventListener("blur", scrollend); + }; + const scroll = _boundEvents.mainContainerScroll = () => { + if (this._isCtrlKeyDown || this._lastScrollTop === mainContainer.scrollTop && this._lastScrollLeft === mainContainer.scrollLeft) { + return; + } + mainContainer.removeEventListener("scroll", scroll, { + passive: true + }); + this._isScrolling = true; + mainContainer.addEventListener("scrollend", scrollend); + mainContainer.addEventListener("blur", scrollend); + }; + mainContainer.addEventListener("scroll", scroll, { + passive: true + }); }, unbindEvents() { throw new Error("Not implemented: unbindEvents"); }, unbindWindowEvents() { - throw new Error("Not implemented: unbindWindowEvents"); + const { + _boundEvents, + appConfig: { + mainContainer + } + } = this; + window.removeEventListener("visibilitychange", webViewerVisibilityChange); + window.removeEventListener("wheel", webViewerWheel, { + passive: false + }); + window.removeEventListener("touchstart", webViewerTouchStart, { + passive: false + }); + window.removeEventListener("touchmove", webViewerTouchMove, { + passive: false + }); + window.removeEventListener("touchend", webViewerTouchEnd, { + passive: false + }); + window.removeEventListener("click", webViewerClick); + window.removeEventListener("keydown", webViewerKeyDown); + window.removeEventListener("keyup", webViewerKeyUp); + window.removeEventListener("resize", _boundEvents.windowResize); + window.removeEventListener("hashchange", _boundEvents.windowHashChange); + window.removeEventListener("beforeprint", _boundEvents.windowBeforePrint); + window.removeEventListener("afterprint", _boundEvents.windowAfterPrint); + window.removeEventListener("updatefromsandbox", _boundEvents.windowUpdateFromSandbox); + mainContainer.removeEventListener("scroll", _boundEvents.mainContainerScroll); + mainContainer.removeEventListener("scrollend", _boundEvents.mainContainerScrollend); + mainContainer.removeEventListener("blur", _boundEvents.mainContainerScrollend); + _boundEvents.removeWindowResolutionChange?.(); + _boundEvents.windowResize = null; + _boundEvents.windowHashChange = null; + _boundEvents.windowBeforePrint = null; + _boundEvents.windowAfterPrint = null; + _boundEvents.windowUpdateFromSandbox = null; + _boundEvents.mainContainerScroll = null; + _boundEvents.mainContainerScrollend = null; }, _accumulateTicks(ticks, prop) { if (this[prop] > 0 && ticks < 0 || this[prop] < 0 && ticks > 0) { @@ -11868,9 +11913,7 @@ function webViewerPageRendered({ } } if (error) { - PDFViewerApplication.l10n.get("pdfjs-rendering-error").then(msg => { - PDFViewerApplication._otherError(msg, error); - }); + PDFViewerApplication._otherError("pdfjs-rendering-error", error); } } function webViewerPageMode({ @@ -12000,9 +12043,6 @@ function webViewerPrint() { function webViewerDownload() { PDFViewerApplication.downloadOrSave(); } -function webViewerOpenInExternalApp() { - PDFViewerApplication.openInExternalApp(); -} function webViewerFirstPage() { PDFViewerApplication.page = 1; } @@ -12591,8 +12631,8 @@ function webViewerReportTelemetry({ -const pdfjsVersion = "4.1.249"; -const pdfjsBuild = "d07f37f44"; +const pdfjsVersion = "4.1.342"; +const pdfjsBuild = "e384df6f1"; const AppConstants = null; window.PDFViewerApplication = PDFViewerApplication; window.PDFViewerApplicationConstants = AppConstants; @@ -12718,7 +12758,8 @@ function getViewerConfiguration() { editorInkThickness: document.getElementById("editorInkThickness"), editorInkOpacity: document.getElementById("editorInkOpacity"), editorStampAddImage: document.getElementById("editorStampAddImage"), - editorFreeHighlightThickness: document.getElementById("editorFreeHighlightThickness") + editorFreeHighlightThickness: document.getElementById("editorFreeHighlightThickness"), + editorHighlightShowAll: document.getElementById("editorHighlightShowAll") }, printContainer: document.getElementById("printContainer") }; diff --git a/toolkit/components/pdfjs/jar.mn b/toolkit/components/pdfjs/jar.mn index e41afee7e1..52199085ad 100644 --- a/toolkit/components/pdfjs/jar.mn +++ b/toolkit/components/pdfjs/jar.mn @@ -18,7 +18,6 @@ pdfjs.jar: content/web/viewer.css (content/web/viewer-geckoview.css) content/web/viewer.mjs (content/web/viewer-geckoview.mjs) content/web/images/gv-toolbarButton-download.svg (content/web/images/gv-toolbarButton-download.svg) - content/web/images/gv-toolbarButton-openinapp.svg (content/web/images/gv-toolbarButton-openinapp.svg) #else content/PdfjsParent.sys.mjs (content/PdfjsParent.sys.mjs) content/PdfjsChild.sys.mjs (content/PdfjsChild.sys.mjs) diff --git a/toolkit/components/pdfjs/moz.yaml b/toolkit/components/pdfjs/moz.yaml index 4d22740080..bd85f67f5a 100644 --- 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: 29c493d36bb5a44251804e9204973211172e0412 (2024-03-01T12:08:44Z). - revision: 29c493d36bb5a44251804e9204973211172e0412 + release: e384df6f16b9ad1c55d1bc325bcae7e096ef3f9b (2024-03-27T19:54:30Z). + revision: e384df6f16b9ad1c55d1bc325bcae7e096ef3f9b # The package's license, where possible using the mnemonic from # https://spdx.org/licenses/ diff --git a/toolkit/components/pdfjs/test/browser.toml b/toolkit/components/pdfjs/test/browser.toml index 4d045fd33e..b60d554c24 100644 --- a/toolkit/components/pdfjs/test/browser.toml +++ b/toolkit/components/pdfjs/test/browser.toml @@ -4,6 +4,8 @@ support-files = [ "head.js", ] +["browser_pdfjs_caret_browsing_mode.js"] + ["browser_pdfjs_download_button.js"] ["browser_pdfjs_editing_contextmenu.js"] diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_caret_browsing_mode.js b/toolkit/components/pdfjs/test/browser_pdfjs_caret_browsing_mode.js new file mode 100644 index 0000000000..a7b2a518d9 --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_caret_browsing_mode.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "https://example.com/browser/" + RELATIVE_DIR; +const pdfUrl = TESTROOT + "file_pdfjs_test.pdf"; +const caretBrowsingModePref = "accessibility.browsewithcaret"; + +// Test telemetry. +add_task(async function test() { + let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + let handlerInfo = mimeService.getFromTypeAndExtension( + "application/pdf", + "pdf" + ); + + // Make sure pdf.js is the default handler. + is( + handlerInfo.alwaysAskBeforeHandling, + false, + "pdf handler defaults to always-ask is false" + ); + is( + handlerInfo.preferredAction, + Ci.nsIHandlerInfo.handleInternally, + "pdf handler defaults to internal" + ); + + info("Pref action: " + handlerInfo.preferredAction); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function test_caret_browsing_mode(browser) { + await waitForPdfJS(browser, pdfUrl); + + let promise = BrowserTestUtils.waitForContentEvent( + browser, + "updatedPreference", + false, + null, + true + ); + await SpecialPowers.pushPrefEnv({ + set: [[caretBrowsingModePref, true]], + }); + await promise; + await TestUtils.waitForTick(); + + await SpecialPowers.spawn(browser, [], async function () { + const viewer = content.wrappedJSObject.PDFViewerApplication; + Assert.ok( + viewer.supportsCaretBrowsingMode, + "Caret browsing mode is supported" + ); + }); + + promise = BrowserTestUtils.waitForContentEvent( + browser, + "updatedPreference", + false, + null, + true + ); + await SpecialPowers.popPrefEnv(); + await promise; + await TestUtils.waitForTick(); + + await SpecialPowers.spawn(browser, [], async function () { + const viewer = content.wrappedJSObject.PDFViewerApplication; + Assert.ok( + !viewer.supportsCaretBrowsingMode, + "Caret browsing mode isn't supported" + ); + }); + + await SpecialPowers.spawn(browser, [], async function () { + const viewer = content.wrappedJSObject.PDFViewerApplication; + await viewer.close(); + }); + } + ); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_download_button.js b/toolkit/components/pdfjs/test/browser_pdfjs_download_button.js index 1e3c00620c..1470d80f0e 100644 --- a/toolkit/components/pdfjs/test/browser_pdfjs_download_button.js +++ b/toolkit/components/pdfjs/test/browser_pdfjs_download_button.js @@ -7,7 +7,7 @@ const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; const TESTROOT = "https://example.com/browser/" + RELATIVE_DIR; var MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(window.browsingContext); MockFilePicker.returnValue = MockFilePicker.returnOK; var tempDir; diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_editing_contextmenu.js b/toolkit/components/pdfjs/test/browser_pdfjs_editing_contextmenu.js index d857bb6aac..779a3a6ad4 100644 --- a/toolkit/components/pdfjs/test/browser_pdfjs_editing_contextmenu.js +++ b/toolkit/components/pdfjs/test/browser_pdfjs_editing_contextmenu.js @@ -32,9 +32,9 @@ async function openContextMenuAt(browser, x, y) { * Open a context menu and get the pdfjs entries * @param {Object} browser * @param {Object} box - * @returns {Map<string,HTMLElement>} the pdfjs menu entries. + * @returns {Promise<Map<string,HTMLElement>>} the pdfjs menu entries. */ -async function getContextMenuItems(browser, box) { +function getContextMenuItems(browser, box) { return new Promise(resolve => { setTimeout(async () => { const { x, y, width, height } = box; @@ -48,6 +48,7 @@ async function getContextMenuItems(browser, box) { "context-pdfjs-delete", "context-pdfjs-selectall", "context-sep-pdfjs-selectall", + "context-pdfjs-highlight-selection", ]; await openContextMenuAt(browser, x + width / 2, y + height / 2); @@ -68,7 +69,7 @@ async function getContextMenuItems(browser, box) { * and returs the pdfjs menu entries. * @param {Object} browser * @param {string} selector - * @returns {Map<string,HTMLElement>} the pdfjs menu entries. + * @returns {Promise<Map<string,HTMLElement>>} the pdfjs menu entries. */ async function getContextMenuItemsOn(browser, selector) { const box = await SpecialPowers.spawn( @@ -129,35 +130,36 @@ function assertMenuitems(menuitems, expected) { elmt => !elmt.id.includes("-sep-") && !elmt.hidden && - elmt.getAttribute("disabled") === "false" + ["", "false"].includes(elmt.getAttribute("disabled")) ) .map(elmt => elmt.id), expected ); } -// Text copy, paste, undo, redo, delete and select all in using the context -// menu. -add_task(async function test() { - let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); - let handlerInfo = mimeService.getFromTypeAndExtension( - "application/pdf", - "pdf" - ); - - // Make sure pdf.js is the default handler. - is( - handlerInfo.alwaysAskBeforeHandling, - false, - "pdf handler defaults to always-ask is false" - ); - is( - handlerInfo.preferredAction, - Ci.nsIHandlerInfo.handleInternally, - "pdf handler defaults to internal" +async function waitAndCheckEmptyContextMenu(browser) { + // check that PDF is opened with internal viewer + await waitForPdfJSAllLayers(browser, TESTROOT + "file_pdfjs_test.pdf", [ + ["annotationEditorLayer", "annotationLayer", "textLayer", "canvasWrapper"], + ["annotationEditorLayer", "textLayer", "canvasWrapper"], + ]); + + const spanBox = await getSpanBox(browser, "and found references"); + const menuitems = await getContextMenuItems(browser, spanBox); + + // Nothing have been edited, hence the context menu doesn't contain any + // pdf entries. + Assert.ok( + [...menuitems.values()].every(elmt => elmt.hidden), + "No visible pdf menuitem" ); + await hideContextMenu(browser); +} - info("Pref action: " + handlerInfo.preferredAction); +// Text copy, paste, undo, redo, delete and select all in using the context +// menu. +add_task(async function test_copy_paste_undo_redo() { + makePDFJSHandler(); await BrowserTestUtils.withNewTab( { gBrowser, url: "about:blank" }, @@ -168,27 +170,8 @@ add_task(async function test() { set: [["pdfjs.annotationEditorMode", 0]], }); - // check that PDF is opened with internal viewer - await waitForPdfJSAllLayers(browser, TESTROOT + "file_pdfjs_test.pdf", [ - [ - "annotationEditorLayer", - "annotationLayer", - "textLayer", - "canvasWrapper", - ], - ["annotationEditorLayer", "textLayer", "canvasWrapper"], - ]); - + await waitAndCheckEmptyContextMenu(browser); const spanBox = await getSpanBox(browser, "and found references"); - let menuitems = await getContextMenuItems(browser, spanBox); - - // Nothing have been edited, hence the context menu doesn't contain any - // pdf entries. - Assert.ok( - [...menuitems.values()].every(elmt => elmt.hidden), - "No visible pdf menuitem" - ); - await hideContextMenu(browser); await enableEditor(browser, "FreeText"); await addFreeText(browser, "hello", spanBox); @@ -211,7 +194,7 @@ add_task(async function test() { Assert.equal(await countElements(browser, ".selectedEditor"), 0); - menuitems = await getContextMenuItems(browser, spanBox); + let menuitems = await getContextMenuItems(browser, spanBox); assertMenuitems(menuitems, [ "context-pdfjs-undo", // Last created editor is undoable "context-pdfjs-selectall", // and selectable. @@ -374,6 +357,73 @@ add_task(async function test() { await SpecialPowers.spawn(browser, [], async function () { var viewer = content.wrappedJSObject.PDFViewerApplication; + viewer.pdfDocument.annotationStorage.resetModified(); + await viewer.close(); + }); + } + ); +}); + +add_task(async function test_highlight_selection() { + makePDFJSHandler(); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + await SpecialPowers.pushPrefEnv({ + set: [ + ["pdfjs.annotationEditorMode", 0], + ["pdfjs.enableHighlightEditor", true], + ], + }); + + await waitAndCheckEmptyContextMenu(browser); + const spanBox = await getSpanBox(browser, "and found references"); + + const changePromise = BrowserTestUtils.waitForContentEvent( + browser, + "annotationeditorstateschanged", + false, + null, + true + ); + await clickAt( + browser, + spanBox.x + spanBox.width / 2, + spanBox.y + spanBox.height / 2, + 2 + ); + await changePromise; + await TestUtils.waitForTick(); + + const mozBox = await getSpanBox(browser, "Mozilla automated testing"); + const menuitems = await getContextMenuItems(browser, mozBox); + + assertMenuitems(menuitems, ["context-pdfjs-highlight-selection"]); + + const telemetryPromise = BrowserTestUtils.waitForContentEvent( + browser, + "reporttelemetry", + false, + null, + true + ); + await clickOnItem( + browser, + menuitems, + "context-pdfjs-highlight-selection" + ); + await telemetryPromise; + + Assert.equal( + await countElements(browser, ".highlightEditor"), + 1, + "An highlight editor must have been added" + ); + + await SpecialPowers.spawn(browser, [], async function () { + var viewer = content.wrappedJSObject.PDFViewerApplication; + viewer.pdfDocument.annotationStorage.resetModified(); await viewer.close(); }); } diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_nonpdf_filename.js b/toolkit/components/pdfjs/test/browser_pdfjs_nonpdf_filename.js index ee08cd45d1..e1723bef6a 100644 --- a/toolkit/components/pdfjs/test/browser_pdfjs_nonpdf_filename.js +++ b/toolkit/components/pdfjs/test/browser_pdfjs_nonpdf_filename.js @@ -13,7 +13,7 @@ const LINK_PAGE_URL = TESTROOT + "file_pdf_download_link.html"; add_task(async function test_filename_nonpdf_extension() { var MockFilePicker = SpecialPowers.MockFilePicker; - MockFilePicker.init(window); + MockFilePicker.init(window.browsingContext); let filepickerNamePromise = new Promise(resolve => { MockFilePicker.showCallback = function (fp) { resolve(fp.defaultString); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_octet_stream.js b/toolkit/components/pdfjs/test/browser_pdfjs_octet_stream.js index d2b4fe310f..da630f726c 100644 --- a/toolkit/components/pdfjs/test/browser_pdfjs_octet_stream.js +++ b/toolkit/components/pdfjs/test/browser_pdfjs_octet_stream.js @@ -5,7 +5,7 @@ const TESTROOT = getRootDirectory(gTestPath).replace( "chrome://mochitests/content/", - "http://mochi.test:8888/" + "https://example.com/" ); // Get a ref to the pdf we want to open. diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_saveas.js b/toolkit/components/pdfjs/test/browser_pdfjs_saveas.js index a1bfc18a91..dcb77c25fe 100644 --- a/toolkit/components/pdfjs/test/browser_pdfjs_saveas.js +++ b/toolkit/components/pdfjs/test/browser_pdfjs_saveas.js @@ -7,7 +7,7 @@ const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; var MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(window.browsingContext); Services.scriptloader.loadSubScript( "chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js", diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_stamp_telemetry.js b/toolkit/components/pdfjs/test/browser_pdfjs_stamp_telemetry.js index b8955f77e3..2ad0179cdc 100644 --- a/toolkit/components/pdfjs/test/browser_pdfjs_stamp_telemetry.js +++ b/toolkit/components/pdfjs/test/browser_pdfjs_stamp_telemetry.js @@ -10,7 +10,7 @@ Services.scriptloader.loadSubScript( ); const MockFilePicker = SpecialPowers.MockFilePicker; -MockFilePicker.init(window); +MockFilePicker.init(window.browsingContext); MockFilePicker.returnValue = MockFilePicker.returnOK; const file = new FileUtils.File(getTestFilePath("moz.png")); MockFilePicker.setFiles([file]); diff --git a/toolkit/components/pdfjs/test/head.js b/toolkit/components/pdfjs/test/head.js index 04c9543b5d..99e2bc7fb7 100644 --- a/toolkit/components/pdfjs/test/head.js +++ b/toolkit/components/pdfjs/test/head.js @@ -426,3 +426,25 @@ async function cleanupDownloads(listId = Downloads.PUBLIC) { await download.finalize(); } } + +function makePDFJSHandler() { + let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + let handlerInfo = mimeService.getFromTypeAndExtension( + "application/pdf", + "pdf" + ); + + // Make sure pdf.js is the default handler. + is( + handlerInfo.alwaysAskBeforeHandling, + false, + "pdf handler defaults to always-ask is false" + ); + is( + handlerInfo.preferredAction, + Ci.nsIHandlerInfo.handleInternally, + "pdf handler defaults to internal" + ); + + info("Pref action: " + handlerInfo.preferredAction); +} |