From 100a5420c8501564564a155e42bc67da091488cb Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Wed, 12 Aug 2026 11:29:52 +0200 Subject: [PATCH 1/5] Use the structure tree to position annotations Serialized OBJR children used raw object references that do not match annotation-layer element IDs. Type correction also required a structure element to have exactly one child, so links with marked content were missed. Match OBJR children to annotations by object reference. Keep structured annotations owned by the structure tree, and use text-layer positioning only for annotations absent from it. --- src/core/annotation.js | 3 +- src/core/struct_tree.js | 37 ++++++----- src/display/annotation_layer.js | 9 ++- test/integration/accessibility_spec.mjs | 14 ++--- test/unit/api_spec.js | 12 +++- test/unit/struct_tree_layer_builder_spec.js | 69 ++++++++++++++++++++- web/struct_tree_layer_builder.js | 30 +++++++++ 7 files changed, 142 insertions(+), 32 deletions(-) diff --git a/src/core/annotation.js b/src/core/annotation.js index 39ed7982bb2bb..5934f251952b0 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -757,7 +757,8 @@ class Annotation { annotationGlobals.structTreeRoot.addAnnotationIdToPage( params.pageRef, - structParent + structParent, + this.ref ); } diff --git a/src/core/struct_tree.js b/src/core/struct_tree.js index 3bfa19fb6073e..5ac0f6ca8a01f 100644 --- a/src/core/struct_tree.js +++ b/src/core/struct_tree.js @@ -101,17 +101,22 @@ class StructTreeRoot { : -1; } - #addIdToPage(pageRef, id, type) { + #addIdToPage(pageRef, id, type, objId) { if (!(pageRef instanceof Ref) || id < 0) { return; } (this.structParentIds ??= new RefMap()) .getOrPutComputed(pageRef, makeArr) - .push([id, type]); + .push([id, type, objId]); } - addAnnotationIdToPage(pageRef, id) { - this.#addIdToPage(pageRef, id, StructElementType.ANNOTATION); + addAnnotationIdToPage(pageRef, id, ref) { + this.#addIdToPage( + pageRef, + id, + StructElementType.ANNOTATION, + ref instanceof Ref ? ref.toString() : null + ); } static async canCreateStructureTree({ @@ -924,18 +929,20 @@ class StructTreePage { if (!ids) { return; } - for (const [elemId, type] of ids) { + for (const [elemId, type, objId] of ids) { const obj = parentTree.get(elemId); - if (obj) { - const elem = this.addNode(this.xref.fetchIfRef(obj), map); - if ( - elem?.kids?.length === 1 && - elem.kids[0].type === StructElementType.OBJECT - ) { - // The node in the struct tree is wrapping an object (annotation - // or xobject), so we need to update the type of the node to match - // the type of the object. - elem.kids[0].type = type; + if (!obj) { + continue; + } + const elem = this.addNode(this.xref.fetchIfRef(obj), map); + if (!elem || !objId) { + continue; + } + // Match the annotation by object reference because its structure + // element may have other children. + for (const kid of elem.kids) { + if (kid.type === StructElementType.OBJECT && kid.refObjId === objId) { + kid.type = type; } } } diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index c265c67de58d1..4d4bc35b2dbcb 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -4212,9 +4212,14 @@ class AnnotationLayer { this.div.append(fragment); await Promise.all(promises); if (this.#accessibilityManager) { - for (const element of this.#elements) { + const annotationIds = await this.#structTreeLayer?.getAnnotationIds(); + for (const { contentElement } of this.#elements) { + if (annotationIds?.has(contentElement.id)) { + // The structure tree already positions this annotation. + continue; + } this.#accessibilityManager.addPointerInTextLayer( - element.contentElement, + contentElement, /* isRemovable = */ false ); } diff --git a/test/integration/accessibility_spec.mjs b/test/integration/accessibility_spec.mjs index 518a418674eb5..5cafb3c0cde5c 100644 --- a/test/integration/accessibility_spec.mjs +++ b/test/integration/accessibility_spec.mjs @@ -230,16 +230,12 @@ describe("accessibility", () => { pages.map(async ([browserName, page]) => { await page.waitForSelector(".structTree"); - const isLinkedToStampAnnotation = await page.$eval( - ".structTree [role='figure']", - el => - document - .getElementById(el.getAttribute("aria-owns")) - .classList.contains("stampAnnotation") + const owners = await page.$$eval( + `[aria-owns~="pdfjs_internal_id_20R"]`, + elements => + elements.map(element => element.closest(".structTree") !== null) ); - expect(isLinkedToStampAnnotation) - .withContext(`In ${browserName}`) - .toBeTrue(); + expect(owners).withContext(`In ${browserName}`).toEqual([true]); }) ); }); diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index 7b1e5e6fbf084..4a976b987a880 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -4899,7 +4899,10 @@ have written that much by now. So, here’s to squashing bugs.`); { role: "Link", children: [ - { type: "object", id: "432R" }, + { + type: "annotation", + id: "pdfjs_internal_id_432R", + }, { type: "content", id: "p406R_mc34" }, ], }, @@ -4922,7 +4925,10 @@ have written that much by now. So, here’s to squashing bugs.`); { role: "Link", children: [ - { type: "object", id: "433R" }, + { + type: "annotation", + id: "pdfjs_internal_id_433R", + }, { type: "content", id: "p406R_mc36" }, ], }, @@ -4948,7 +4954,7 @@ have written that much by now. So, here’s to squashing bugs.`); { role: "Link", children: [ - { type: "object", id: "434R" }, + { type: "annotation", id: "pdfjs_internal_id_434R" }, { type: "content", id: "p406R_mc10" }, ], }, diff --git a/test/unit/struct_tree_layer_builder_spec.js b/test/unit/struct_tree_layer_builder_spec.js index 52bf3e87cd41d..dc19d0fe6aa0a 100644 --- a/test/unit/struct_tree_layer_builder_spec.js +++ b/test/unit/struct_tree_layer_builder_spec.js @@ -17,10 +17,14 @@ import { isNodeJS } from "../../src/shared/util.js"; import { StructTreeLayerBuilder } from "../../web/struct_tree_layer_builder.js"; describe("StructTreeLayerBuilder", function () { - function render(structTree) { + function build(structTree) { return new StructTreeLayerBuilder({ getStructTree: async () => structTree, - }).render(); + }); + } + + function render(structTree) { + return build(structTree).render(); } beforeEach(function () { @@ -356,6 +360,67 @@ describe("StructTreeLayerBuilder", function () { expect(row.id).toEqual(""); }); + it("keeps structured annotations in the structure tree", async function () { + const builder = build({ + role: "Root", + children: [ + { + role: "P", + children: [ + { type: "content", id: "p1R_mc0" }, + { + role: "Link", + children: [ + { + role: "Figure", + children: [{ type: "content", id: "p1R_mc1" }], + }, + { type: "annotation", id: "pdfjs_internal_id_1R" }, + ], + }, + { + role: "Link", + children: [ + { type: "content", id: "p1R_mc2" }, + { type: "annotation", id: "pdfjs_internal_id_2R" }, + { type: "content", id: "p1R_mc3" }, + { type: "annotation", id: "pdfjs_internal_id_3R" }, + ], + }, + { + role: "Link", + children: [{ type: "annotation", id: "pdfjs_internal_id_4R" }], + }, + ], + }, + ], + }); + const tree = await builder.render(); + + expect(await builder.getAnnotationIds()).toEqual( + new Set([ + "pdfjs_internal_id_1R", + "pdfjs_internal_id_2R", + "pdfjs_internal_id_3R", + "pdfjs_internal_id_4R", + ]) + ); + expect( + [...tree.querySelectorAll("[aria-owns]")].map(e => + e.getAttribute("aria-owns") + ) + ).toEqual([ + "p1R_mc0", + "p1R_mc1", + "pdfjs_internal_id_1R", + "p1R_mc2", + "pdfjs_internal_id_2R", + "p1R_mc3", + "pdfjs_internal_id_3R", + "pdfjs_internal_id_4R", + ]); + }); + it("only uses the caption role inside a table or a figure", async function () { const tree = await render({ role: "Root", diff --git a/web/struct_tree_layer_builder.js b/web/struct_tree_layer_builder.js index 2ab554b9af591..1db99a9c1cd1b 100644 --- a/web/struct_tree_layer_builder.js +++ b/web/struct_tree_layer_builder.js @@ -193,6 +193,8 @@ class StructTreeLayerBuilder { #treePromise; + #annotationIds = new Set(); + #elementAttributes = new Map(); #structElementIdPrefix = `pdfjs_internal_struct_${getUuid()}_`; @@ -230,6 +232,7 @@ class StructTreeLayerBuilder { try { const tree = await this.#promise; this.#collectStructElements(tree); + this.#collectAnnotations(tree); this.#treeDom = this.#walk(tree); } catch (ex) { reject(ex); @@ -253,6 +256,20 @@ class StructTreeLayerBuilder { return null; } + /** + * Get the ids of annotations owned by the structure tree. + * @returns {Promise|null>} + */ + async getAnnotationIds() { + try { + await this.render(); + return this.#annotationIds; + } catch { + // See the comment in `getAriaAttributes`. + } + return null; + } + hide() { if (this.#treeDom && !this.#treeDom.hidden) { this.#treeDom.hidden = true; @@ -279,6 +296,19 @@ class StructTreeLayerBuilder { } } + #collectAnnotations(node) { + if (!node) { + return; + } + if (node.type === "annotation") { + this.#annotationIds.add(node.id); + return; + } + for (const child of node.children || []) { + this.#collectAnnotations(child); + } + } + #getStructElementId(structId) { return this.#structElementIds.getOrInsertComputed( structId, From fd8ed8422fa71de847b795560098be939b7d2a00 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Thu, 13 Aug 2026 21:49:26 +0200 Subject: [PATCH 2/5] Nova-ify the viewer (bug 2063405) --- external/stylelint/mozcentral-tokens.css | 91 ++++++ stylelint-mozcentral.json | 1 + web/annotation_editor_layer_builder.css | 112 +++++++ web/app_options.js | 6 +- web/buttons.css | 10 + web/comment_manager.css | 54 ++++ web/dialog.css | 9 + web/digital_signature_properties.css | 64 ++++ web/images/toolbarButton-menuArrowNova.svg | 3 + web/menu.css | 64 ++++ web/message_bar.css | 16 + web/pdf_viewer.css | 16 + web/sidebar.css | 17 ++ web/signature_manager.css | 35 +++ web/tree.css | 37 +++ web/viewer.css | 334 ++++++++++++++++++++- web/views_manager.css | 109 +++++++ 17 files changed, 969 insertions(+), 9 deletions(-) create mode 100644 external/stylelint/mozcentral-tokens.css create mode 100644 web/images/toolbarButton-menuArrowNova.svg diff --git a/external/stylelint/mozcentral-tokens.css b/external/stylelint/mozcentral-tokens.css new file mode 100644 index 0000000000000..4f02be1fb03be --- /dev/null +++ b/external/stylelint/mozcentral-tokens.css @@ -0,0 +1,91 @@ +/* Copyright 2026 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +:root { + --background-color-box: initial; + --background-color-box-info: initial; + --background-color-canvas: initial; + --background-color-critical: initial; + --background-color-information: initial; + --background-color-success: initial; + --border-color: initial; + --border-color-deemphasized: initial; + --border-color-interactive: initial; + --border-color-interactive-hover: initial; + --border-radius-circle: initial; + --border-radius-large: initial; + --border-radius-medium: initial; + --border-radius-small: initial; + --border-radius-xsmall: initial; + --box-shadow-level-2: initial; + --box-shadow-level-3: initial; + --button-background-color: initial; + --button-background-color-active: initial; + --button-background-color-ghost-hover: initial; + --button-background-color-hover: initial; + --button-background-color-menu-active: initial; + --button-background-color-menu-hover: initial; + --button-border: initial; + --button-border-color: initial; + --button-border-color-active: initial; + --button-border-color-hover: initial; + --button-border-radius: initial; + --button-font-weight: initial; + --button-min-height: initial; + --button-opacity-disabled: initial; + --button-padding-block: initial; + --button-padding-inline: initial; + --button-text-color: initial; + --button-text-color-hover: initial; + --button-text-color-menu-active: initial; + --button-text-color-menu-hover: initial; + --button-text-color-primary: initial; + --color-accent-attention: initial; + --color-accent-primary: initial; + --color-accent-primary-active: initial; + --color-gray-15: initial; + --color-gray-85: initial; + --focus-outline: initial; + --focus-outline-inset: initial; + --focus-outline-offset: initial; + --font-size-root: initial; + --font-size-small: initial; + --font-weight: initial; + --icon-color: initial; + --icon-color-critical: initial; + --icon-color-information: initial; + --input-search-border-radius: initial; + --link-color: initial; + --link-color-hover: initial; + --panel-border-radius: initial; + --panel-menuitem-border-radius: initial; + --panel-menuitem-margin: initial; + --panel-menuitem-margin-inline: initial; + --panel-menuitem-padding: initial; + --panel-menuitem-padding-inline: initial; + --size-item-large: initial; + --size-item-medium: initial; + --space-large: initial; + --space-medium: initial; + --space-small: initial; + --space-xsmall: initial; + --space-xxsmall: initial; + --text-color: initial; + --text-color-deemphasized: initial; + --toolbar-background-color: initial; + --toolbarbutton-background-color-active: initial; + --toolbarbutton-background-color-hover: initial; + --toolbarbutton-icon-fill: initial; +} diff --git a/stylelint-mozcentral.json b/stylelint-mozcentral.json index 6f5e7fffc1f3c..cc96a754dd32b 100644 --- a/stylelint-mozcentral.json +++ b/stylelint-mozcentral.json @@ -1,5 +1,6 @@ { "plugins": ["./external/stylelint/no-unused-custom-properties.mjs"], + "referenceFiles": ["./external/stylelint/mozcentral-tokens.css"], "rules": { "no-unknown-custom-properties": true, "pdfjs/no-unused-custom-properties": [ diff --git a/web/annotation_editor_layer_builder.css b/web/annotation_editor_layer_builder.css index bb011844b8e92..13f4768c4d334 100644 --- a/web/annotation_editor_layer_builder.css +++ b/web/annotation_editor_layer_builder.css @@ -267,6 +267,21 @@ --alt-text-hover-done-color: var(--alt-text-done-color); --alt-text-hover-warning-color: var(--alt-text-warning-color); + /* Match Nova's panel surface and 32px toolbar controls. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --editor-toolbar-bg-color: var(--background-color-box); + --editor-toolbar-fg-color: var(--toolbarbutton-icon-fill); + --editor-toolbar-border-color: var(--border-color-deemphasized); + --editor-toolbar-hover-bg-color: var( + --toolbarbutton-background-color-hover + ); + --editor-toolbar-shadow: var(--box-shadow-level-2); + --editor-toolbar-height: var(--size-item-large); + --alt-text-warning-color: var(--icon-color-information); + } + /*#endif*/ + @media screen and (forced-colors: active) { --editor-toolbar-bg-color: var(--button-background-color, ButtonFace); --editor-toolbar-fg-color: var(--button-text-color, ButtonText); @@ -320,6 +335,12 @@ border: 1px solid var(--editor-toolbar-border-color); box-shadow: var(--editor-toolbar-shadow); + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--panel-border-radius); + } + /*#endif*/ + &.hidden { display: none; } @@ -413,6 +434,15 @@ border-radius: 2px; outline: 2px solid var(--editor-toolbar-focus-outline-color); } + + /* Match the radius of hovered and focused Nova buttons. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + &:is(:hover, :focus-visible) { + border-radius: var(--button-border-radius); + } + } + /*#endif*/ } .altText { @@ -493,6 +523,17 @@ --alt-text-tooltip-shadow: 0 2px 6px 0 light-dark(rgb(58 57 68 / 0.2), #15141a); + /* Use Nova's box surface and compact radius. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --alt-text-tooltip-bg: var(--background-color-box); + --alt-text-tooltip-border: var(--border-color-deemphasized); + --alt-text-tooltip-shadow: var(--box-shadow-level-2); + + border-radius: var(--border-radius-xsmall); + } + /*#endif*/ + @media screen and (forced-colors: active) { --alt-text-tooltip-bg: Canvas; --alt-text-tooltip-fg: var(--text-color, CanvasText); @@ -619,6 +660,15 @@ --no-alt-text-badge-bg-color: light-dark(#cfcfd8, #fbfbfe); --no-alt-text-badge-fg-color: light-dark(#5b5b66, #15141a); + /* Use Nova's neutral badge colors. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --no-alt-text-badge-border-color: var(--border-color-deemphasized); + --no-alt-text-badge-bg-color: var(--background-color-box); + --no-alt-text-badge-fg-color: var(--icon-color); + } + /*#endif*/ + @media screen and (forced-colors: active) { --no-alt-text-badge-border-color: ButtonText; --no-alt-text-badge-bg-color: ButtonFace; @@ -641,6 +691,12 @@ border: 1px solid var(--no-alt-text-badge-border-color); background: var(--no-alt-text-badge-bg-color); + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--border-radius-xsmall); + } + /*#endif*/ + &::before { content: ""; display: inline-block; @@ -906,6 +962,13 @@ --preview-image-bg-color: light-dark(#f0f0f4, #2b2a33); --preview-image-border: none; + /* Use Nova's secondary box surface for the preview. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --preview-image-bg-color: var(--background-color-box-info); + } + /*#endif*/ + @media screen and (forced-colors: active) { --preview-image-bg-color: ButtonFace; --preview-image-border: 1px solid ButtonText; @@ -1089,6 +1152,14 @@ --selected-outline-color: light-dark(#0060df, #aaf2ff); --swatch-border-color: light-dark(#cfcfd8, #52525e); + /* Use Nova selection and border colors for the swatches. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --selected-outline-color: var(--color-accent-primary); + --swatch-border-color: var(--border-color-deemphasized); + } + /*#endif*/ + @media screen and (forced-colors: active) { --hover-outline-color: Highlight; --selected-outline-color: var(--hover-outline-color); @@ -1113,6 +1184,15 @@ .basicColorPicker { width: 28px; + /* Keep the swatch square when the row height changes. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + width: auto; + aspect-ratio: 1; + padding: var(--space-small); + } + /*#endif*/ + &::-moz-color-swatch { border-radius: 100%; margin: 0; @@ -1219,6 +1299,14 @@ &:has(.dropdown:not(.hidden)) { background-color: var(--editor-toolbar-hover-bg-color); + /* Match the active toolbar-button state while the menu is open. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + background-color: var(--toolbarbutton-background-color-active); + border-radius: var(--button-border-radius); + } + /*#endif*/ + &::after { scale: -1; } @@ -1265,6 +1353,30 @@ outline: 2px solid var(--hover-outline-color); } } + + /* Match Firefox's horizontal visual picker. Keep this after the + button rule to override its width: 100%. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + flex-direction: row; + gap: var(--space-small); + padding: var(--space-small); + width: max-content; + inset-inline-start: 50%; + translate: calc(-50% * var(--dir-factor)); + border-radius: var(--border-radius-medium); + + button { + width: auto; + flex: 0 0 auto; + + > .swatch { + width: var(--size-item-medium); + height: var(--size-item-medium); + } + } + } + /*#endif*/ } } } diff --git a/web/app_options.js b/web/app_options.js index 23a036dce1544..0078c917e2c15 100644 --- a/web/app_options.js +++ b/web/app_options.js @@ -417,10 +417,8 @@ const defaultOptions = new Map([ }, ], [ - // Whether the viewer follows the Firefox design system (see the pref-gated - // @import of tokens-brand.css in viewer.css). Read from CSS via - // -moz-pref(), not from JS, so it is Firefox-only and has no effect - // elsewhere. + // Import Firefox design-system tokens and enable Nova viewer styles. The + // Nova overrides also require browser.nova.enabled. "enableNova", { /** @type {boolean} */ diff --git a/web/buttons.css b/web/buttons.css index cd49b8e17d252..f4d5d396e2edf 100644 --- a/web/buttons.css +++ b/web/buttons.css @@ -82,6 +82,16 @@ --hover-filter: brightness(1.4); } + /* Nova supplies state colors; preserve its border and disabled opacity. */ + /*#if MOZCENTRAL && !GECKOVIEW*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --hover-filter: none; + --button-disabled-opacity: var(--button-opacity-disabled); + --button-secondary-hover-border-color: var(--button-border-color-hover); + --button-secondary-active-border-color: var(--button-border-color-active); + } + /*#endif*/ + @media screen and (forced-colors: active) { --button-primary-bg-color: var( --button-background-color-primary, diff --git a/web/comment_manager.css b/web/comment_manager.css index c79a7886aad18..f74acac44d8a3 100644 --- a/web/comment_manager.css +++ b/web/comment_manager.css @@ -25,6 +25,13 @@ box-sizing: border-box; border-radius: 8px; + + /* Match the Nova radius used by .dialog. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--border-radius-medium); + } + /*#endif*/ } #commentManagerDialog { @@ -264,6 +271,26 @@ --button-comment-hover-bg: light-dark(#e0e0e6, #52525e); --button-comment-hover-color: var(--button-comment-color); + /* Use Nova's secondary box surface and toolbar-button states. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --comment-date-fg-color: color-mix( + in srgb, + var(--comment-fg-color) 69%, + transparent + ); + --comment-bg-color: var(--background-color-box-info); + --comment-border-color: var(--border-color-deemphasized); + --comment-count-bg-color: var(--background-color-information); + --comment-indicator-active-fg-color: var(--color-accent-primary-active); + --comment-indicator-focus-fg-color: var(--icon-color); + + --button-comment-color: var(--toolbarbutton-icon-fill); + --button-comment-active-bg: var(--toolbarbutton-background-color-active); + --button-comment-hover-bg: var(--toolbarbutton-background-color-hover); + } + /*#endif*/ + @media screen and (forced-colors: active) { --comment-date-fg-color: CanvasText; --comment-bg-color: Canvas; @@ -334,6 +361,13 @@ font-style: normal; font-weight: 400; line-height: normal; + + /* Use Nova's small radius. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--border-radius-small); + } + /*#endif*/ } } @@ -384,6 +418,18 @@ height: 0; overflow: hidden; } + + /* Match Nova toolbar-button radius and focus styling. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--button-border-radius); + + &:focus-visible { + outline: var(--focus-outline); + outline-offset: var(--focus-outline-offset); + } + } + /*#endif*/ } } @@ -413,6 +459,14 @@ border: 0.5px solid var(--comment-border-color); background-color: var(--comment-bg-color); + /* Use Nova's radius and 1px separator. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--border-radius-medium); + border-width: 1px; + } + /*#endif*/ + &:not(.noComments) { &:hover { @media screen and (forced-colors: active) { diff --git a/web/dialog.css b/web/dialog.css index ac98c2bbafc5f..d550f6d707bcf 100644 --- a/web/dialog.css +++ b/web/dialog.css @@ -36,6 +36,15 @@ --input-text-bg-color: light-dark(white, #42414d); --input-text-fg-color: var(--text-primary-color); + /* Match Nova popup borders, separators, and shadows. */ + /*#if MOZCENTRAL && !GECKOVIEW*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --dialog-border-color: var(--border-color-deemphasized); + --dialog-shadow: var(--box-shadow-level-3); + --separator-color: var(--border-color-deemphasized); + } + /*#endif*/ + @media screen and (forced-colors: active) { --dialog-bg-color: var(--background-color-canvas, Canvas); /* border-only: --background-color-canvas would resolve to Canvas in FF HCM diff --git a/web/digital_signature_properties.css b/web/digital_signature_properties.css index 769dc0807334c..6b3475ea5a9be 100644 --- a/web/digital_signature_properties.css +++ b/web/digital_signature_properties.css @@ -73,6 +73,27 @@ light-dark(rgb(29 142 61), rgb(106 210 126)) ); + /* Like Nova message bars, use a neutral banner surface and keep severity in + the colored stripe. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --sig-card-border: var(--border-color-deemphasized); + --sig-card-nested-bg: var(--background-color-box-info); + --sig-row-color: var(--text-color); + --sig-detail-color: var(--text-color-deemphasized); + --sig-divider-color: var(--border-color-deemphasized); + --sig-summary-hover-color: var(--link-color-hover); + --sig-link-hover-bg: var(--button-background-color-ghost-hover); + --sig-banner-verified-bg: var(--background-color-box-info); + --sig-banner-warn-bg: var(--background-color-box-info); + --sig-banner-error-bg: var(--background-color-box-info); + --sig-banner-verified-color: var(--text-color); + --sig-banner-warn-color: var(--text-color); + --sig-banner-error-color: var(--text-color); + --sig-icon-default: var(--text-color-deemphasized); + } + /*#endif*/ + @media screen and (forced-colors: active) { /* HCM keywords are picked by *semantic role*, not by hue — the * user's high-contrast theme resolves them to whatever palette it @@ -222,6 +243,13 @@ * user's high-contrast palette without us hard-coding any hue. */ border-inline-start: 3px solid currentcolor; + /* Match the Nova message-bar radius. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--border-radius-medium); + } + /*#endif*/ + &.verified { background: var(--sig-banner-verified-bg); color: var(--sig-banner-verified-color); @@ -258,6 +286,13 @@ gap: 3px; background: var(--sig-card-bg); + /* Match the banner's inset radius. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--border-radius-medium); + } + /*#endif*/ + .signer { font-weight: 600; font-size: 13px; @@ -361,6 +396,14 @@ border-radius: 4px; white-space: nowrap; + /* Use Nova ghost-button spacing and radius. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + padding-inline: var(--space-small); + border-radius: var(--button-border-radius); + } + /*#endif*/ + &:hover { background: var(--sig-link-hover-bg); text-decoration: underline; @@ -368,6 +411,13 @@ &:focus-visible { outline: 2px solid var(--sig-link-color); outline-offset: 1px; + + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + outline: var(--focus-outline); + outline-offset: var(--focus-outline-offset); + } + /*#endif*/ } } @@ -377,6 +427,13 @@ padding-top: 4px; font-size: 12px; + /* Use Nova's solid separator. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-top-style: solid; + } + /*#endif*/ + > summary { cursor: pointer; user-select: none; @@ -426,6 +483,13 @@ padding: 6px 8px; background: var(--sig-card-nested-bg); gap: 2px; + + /* Use the radius below the outer card's. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--border-radius-small); + } + /*#endif*/ } .subSignatures .signer, diff --git a/web/images/toolbarButton-menuArrowNova.svg b/web/images/toolbarButton-menuArrowNova.svg new file mode 100644 index 0000000000000..f5c80208f990a --- /dev/null +++ b/web/images/toolbarButton-menuArrowNova.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/menu.css b/web/menu.css index 3ff790dba5d0b..3d225ed5c3f65 100644 --- a/web/menu.css +++ b/web/menu.css @@ -66,6 +66,23 @@ button.hasPopupMenu { --menuitem-hover-background-blend-mode: normal; --disabled-opacity: 0.62; + /* Map menu states to Nova panel and menu-item tokens. Keep this before the + forced-colors overrides. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --menuitem-gap: var(--space-small); + --menuitem-border-radius: var(--panel-menuitem-border-radius); + --menu-box-shadow: var(--box-shadow-level-2); + --menu-border-color: var(--border-color-deemphasized); + --menuitem-hover-bg: var(--button-background-color-menu-hover); + --menuitem-text-hover-fg: var(--button-text-color-menu-hover); + --menuitem-active-bg: var(--button-background-color-menu-active); + --menuitem-text-active-fg: var(--button-text-color-menu-active); + --menuitem-focus-border-color: transparent; + --disabled-opacity: var(--button-opacity-disabled); + } + /*#endif*/ + @media screen and (forced-colors: active) { --menu-bg: var(--background-color-box, Canvas); --menu-background-blend-mode: normal; @@ -104,6 +121,14 @@ button.hasPopupMenu { border: 1px solid var(--menu-border-color); backdrop-filter: var(--menu-backdrop-filter); + /* Rows provide their own inline inset. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + padding: var(--space-small) 0; + border-radius: var(--panel-border-radius); + } + /*#endif*/ + &.withMark { --menu-mark-icon-size: 16px; } @@ -185,6 +210,14 @@ button.hasPopupMenu { background-color: var(--menuitem-focus-bg); outline: 2px solid var(--menuitem-focus-outline-color); outline-offset: 2px; + + /* Use the inset focus ring from Firefox panel items. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + outline: var(--focus-outline); + outline-offset: var(--focus-outline-inset); + } + /*#endif*/ } } @@ -215,5 +248,36 @@ button.hasPopupMenu { font-weight: 510; line-height: normal; } + + /* Match Firefox panel-item spacing. width: auto prevents the margins from + overflowing; add checkmark padding only where its size has a unit. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + height: auto; + min-height: var(--size-item-large); + width: auto; + margin: var(--panel-menuitem-margin); + padding: var(--panel-menuitem-padding); + + &.selected::after { + inset-inline-start: var(--panel-menuitem-padding-inline); + } + + > span { + padding-inline-start: 0; + } + } + /*#endif*/ + } + + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + &.withMark > button { + padding-inline-start: calc( + var(--panel-menuitem-padding-inline) + var(--menu-mark-icon-size) + + var(--menuitem-gap) + ); + } } + /*#endif*/ } diff --git a/web/message_bar.css b/web/message_bar.css index 67b5a1fd76ad9..7f7815878c2bc 100644 --- a/web/message_bar.css +++ b/web/message_bar.css @@ -32,6 +32,13 @@ light-dark(rgb(21 20 26 / 0.07), rgb(251 251 254 / 0.07)) ); + /* Use Nova's icon-button radius. */ + /*#if MOZCENTRAL && !GECKOVIEW*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --message-bar-close-button-border-radius: var(--button-border-radius); + } + /*#endif*/ + @media screen and (forced-colors: active) { --message-bar-close-button-color: var(--button-text-color, ButtonText); --message-bar-close-button-border: 1px solid @@ -159,6 +166,15 @@ rgb(255 255 255 / 0.08) ); + /* Match Nova message bars: use a neutral surface and information icon. */ + /*#if MOZCENTRAL && !GECKOVIEW*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --message-bar-icon-color: var(--icon-color-information); + --message-bar-bg-color: var(--background-color-box); + --message-bar-border-color: var(--border-color-deemphasized); + } + /*#endif*/ + @media screen and (forced-colors: active) { --message-bar-icon-color: CanvasText; --message-bar-bg-color: Canvas; diff --git a/web/pdf_viewer.css b/web/pdf_viewer.css index eae6d0e4eee19..b1fd58c080029 100644 --- a/web/pdf_viewer.css +++ b/web/pdf_viewer.css @@ -60,6 +60,15 @@ --new-badge-color: light-dark(#fff, #15141a); --new-badge-border-color: light-dark(#fbfbfe / 40%, #15141a / 40%); + /* Use Nova's semantic success surface and text color. */ + /*#if MOZCENTRAL && !GECKOVIEW*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --new-badge-bg: var(--background-color-success); + --new-badge-color: var(--text-color); + --new-badge-border-color: transparent; + } + /*#endif*/ + @media screen and (forced-colors: active) { --pdfViewer-padding-bottom: 9px; --page-margin: 8px auto -1px; @@ -81,6 +90,13 @@ border-radius: 4px; border: 1px solid var(--new-badge-border-color); padding-inline: 4px; + + /*#if MOZCENTRAL && !GECKOVIEW*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--border-radius-circle); + padding-inline: var(--space-small); + } + /*#endif*/ margin-inline: 4px; font: menu; font-size: 12px; diff --git a/web/sidebar.css b/web/sidebar.css index fe5e9fc618566..de435e9ca184e 100644 --- a/web/sidebar.css +++ b/web/sidebar.css @@ -35,6 +35,15 @@ light-dark(#0062fa, #00cadb) ); + /* Style the sidebar as a Nova panel. Keep this before forced-colors. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --sidebar-border-color: var(--border-color-deemphasized); + --sidebar-box-shadow: var(--box-shadow-level-2); + --sidebar-border-radius: var(--panel-border-radius); + } + /*#endif*/ + @media screen and (forced-colors: active) { --sidebar-bg-color: var(--background-color-box, Canvas); --sidebar-border-color: CanvasText; @@ -81,6 +90,14 @@ background-color: var(--resizer-hover-bg-color); outline: none; } + + /* Match Firefox's Nova splitter: use its full width and rounded ends. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-inline-width: 0; + border-radius: var(--border-radius-large); + } + /*#endif*/ } &.resizing { diff --git a/web/signature_manager.css b/web/signature_manager.css index 592617c3276d8..4e55701a7f6e2 100644 --- a/web/signature_manager.css +++ b/web/signature_manager.css @@ -34,6 +34,14 @@ --button-signature-hover-bg: light-dark(#e0e0e6, #52525e); --button-signature-hover-color: var(--button-signature-color); + /* Use Nova button states for saved-signature rows. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --button-signature-hover-bg: var(--button-background-color-hover); + --button-signature-active-bg: var(--button-background-color-active); + } + /*#endif*/ + @media screen and (forced-colors: active) { --signature-bg: HighlightText; --signature-hover-bg: var(--signature-bg); @@ -374,6 +382,12 @@ margin: 0; background-color: var(--thickness-bg); border-radius: 4px 4px 0 0; + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--border-radius-small) + var(--border-radius-small) 0 0; + } + /*#endif*/ border-inline: var(--thickness-border); border-top: var(--thickness-border); pointer-events: auto; @@ -623,6 +637,11 @@ button { border: var(--button-signature-border); border-radius: 4px; + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--border-radius-small); + } + /*#endif*/ background-color: var(--button-signature-bg); color: var(--button-signature-color); @@ -665,6 +684,11 @@ justify-content: flex-start; outline: none; border-radius: 4px; + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--border-radius-small); + } + /*#endif*/ box-sizing: border-box; font: message-box; position: relative; @@ -686,6 +710,11 @@ box-sizing: border-box; border: none; border-radius: 4px; + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--border-radius-small); + } + /*#endif*/ > path { stroke: var(--button-signature-color); @@ -705,6 +734,12 @@ &:is(:hover, :active) > svg { border-radius: 4px 0 0 4px; + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--border-radius-small) 0 0 + var(--border-radius-small); + } + /*#endif*/ background-color: var(--signature-hover-bg); } diff --git a/web/tree.css b/web/tree.css index 346b62c1d1308..323226fa12a15 100644 --- a/web/tree.css +++ b/web/tree.css @@ -28,6 +28,19 @@ --treeitem-expanded-icon: url(images/treeitem-expanded.svg); --treeitem-collapsed-icon: url(images/treeitem-collapsed.svg); + /* Style tree rows like Nova toolbar buttons and inset them from the sidebar. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --treeitem-color: var(--text-color); + --treeitem-bg-color: var(--toolbarbutton-background-color-hover); + --treeitem-hover-color: var(--text-color); + --treeitem-selected-color: var(--text-color); + --treeitem-selected-bg-color: var(--toolbarbutton-background-color-active); + + padding-inline: var(--panel-menuitem-margin-inline); + } + /*#endif*/ + &.withNesting { .treeItemToggler { &::before { @@ -68,6 +81,13 @@ background-clip: padding-box; border-radius: 2px; color: var(--treeitem-hover-color); + + /* This selector outranks the row's own radius, so repeat it here. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--panel-menuitem-border-radius); + } + /*#endif*/ } } @@ -110,11 +130,28 @@ white-space: normal; cursor: default; + /* Fill the inset width and pad the label inside the rounded row. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + box-sizing: border-box; + min-width: 100%; + padding-block: var(--space-xsmall); + padding-inline: var(--space-small); + border-radius: var(--panel-menuitem-border-radius); + } + /*#endif*/ + &:hover { background-color: var(--treeitem-bg-color); background-clip: padding-box; border-radius: 2px; color: var(--treeitem-hover-color); + + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--panel-menuitem-border-radius); + } + /*#endif*/ } } diff --git a/web/viewer.css b/web/viewer.css index 2b8f3b2dc50ea..2bd6791295085 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -13,11 +13,8 @@ * limitations under the License. */ -/* Pref-gated, so the Firefox tokens are opt-in (and with them the Nova theme, - which tokens-brand.css gates on browser.nova.enabled): with the pref off - every var() falls back to its shipped literal. -moz-pref() works here - because chrome rules are enabled for a resource:// stylesheet, and it only - sees pdfjs.* prefs. */ +/* Import Firefox tokens only when enabled; the token sheet gates its Nova + overrides on browser.nova.enabled. */ /*#if MOZCENTRAL*/ @import url(chrome://global/skin/design-system/tokens-brand.css) -moz-pref( "pdfjs.enableNova" @@ -135,6 +132,61 @@ --secondaryToolbarButton-documentProperties-icon: url(images/secondaryToolbarButton-documentProperties.svg); --editorParams-stampAddImage-icon: url(images/toolbarButton-zoomIn.svg); --comment-edit-button-icon: url(images/comment-editButton.svg); + + /* Map viewer variables to Nova design tokens. Keep Nova blocks before later + forced-colors overrides. Comments must stay outside #if blocks because the + preprocessor strips one comment layer from their contents. The root font + size makes Firefox's rem-based spacing tokens resolve correctly. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + font-size: var(--font-size-root); + + --toolbarButton-menuArrowNova-icon: url(images/toolbarButton-menuArrowNova.svg); + + --toolbar-height: 40px; + --toolbar-horizontal-padding: 6px; + --toolbar-vertical-padding: 4px; + + --toolbar-icon-opacity: 1; + --doorhanger-icon-opacity: 1; + + --main-color: var(--text-color); + --body-bg-color: light-dark(var(--color-gray-15), var(--color-gray-85)); + --progressBar-color: var(--color-accent-primary); + --progressBar-bg-color: var(--background-color-box-info); + --progressBar-blend-color: color-mix( + in srgb, + var(--color-accent-primary) 40%, + var(--background-color-box) + ); + --toolbar-icon-bg-color: var(--toolbarbutton-icon-fill); + --toolbar-icon-hover-bg-color: var(--toolbarbutton-icon-fill); + + --sidebar-narrow-bg-color: color-mix( + in srgb, + var(--background-color-canvas) 90%, + transparent + ); + --sidebar-toolbar-bg-color: var(--toolbar-background-color); + --toolbar-bg-color: var(--toolbar-background-color); + --toolbar-border-color: var(--border-color-deemphasized); + --toggled-btn-color: var(--toolbarbutton-icon-fill); + --toggled-btn-bg-color: var(--toolbarbutton-background-color-active); + --toggled-hover-active-btn-color: var( + --toolbarbutton-background-color-active + ); + --dropdown-btn-bg-color: var(--button-background-color); + --dropdown-btn-border: 1px solid var(--button-border-color); + --separator-color: var(--border-color-deemphasized); + --field-color: var(--text-color); + --field-bg-color: var(--background-color-box); + --field-border-color: var(--border-color-interactive); + --doorhanger-bg-color: var(--background-color-box); + --doorhanger-border-color: var(--border-color-deemphasized); + --doorhanger-hover-color: var(--text-color); + --doorhanger-separator-color: var(--border-color-deemphasized); + } + /*#endif*/ } :root:dir(rtl) { @@ -190,9 +242,23 @@ html { &[data-toolbar-density="compact"] { --toolbar-height: 30px; + + /* 24px buttons, i.e. --size-item-medium. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --toolbar-height: 32px; + } + /*#endif*/ } &[data-toolbar-density="touch"] { --toolbar-height: 44px; + + /* 40px buttons, i.e. --button-min-height-large. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --toolbar-height: 48px; + } + /*#endif*/ } } @@ -393,6 +459,20 @@ body { &::after { border-width: var(--doorhanger-height); } + + /* Nova doorhangers use a bordered panel surface without a caret. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--panel-border-radius); + border: 1px solid var(--doorhanger-border-color); + box-shadow: var(--box-shadow-level-2); + + &::after, + &::before { + display: none; + } + } + /*#endif*/ } .doorHangerRight { @@ -406,6 +486,13 @@ body { border-bottom-color: var(--doorhanger-bg-color); inset-inline-end: 1px; } + + /* Without the caret, align the panel edge to its anchor. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + inset-inline-end: 0; + } + /*#endif*/ } .doorHanger { @@ -419,6 +506,12 @@ body { border-bottom-color: var(--toolbar-bg-color); inset-inline-start: 1px; } + + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + inset-inline-start: 0; + } + /*#endif*/ } .splitToolbarButtonSeparator { @@ -508,6 +601,12 @@ body { height: 9px; width: 9px; border-radius: 50%; + + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + background-color: var(--color-accent-attention); + } + /*#endif*/ } .verticalToolbarSeparator { @@ -568,6 +667,24 @@ body { &:focus { border-color: #0a84ff; } + + /* Use Nova field focus styling. Do not fix the height: Firefox tests that + #pageNumber follows its font size. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--border-radius-small); + padding: var(--space-xsmall) var(--space-small); + + &:focus { + border-color: var(--field-border-color); + } + + &:focus-visible { + outline: var(--focus-outline); + outline-offset: var(--focus-outline-inset); + } + } + /*#endif*/ } #pageNumber { @@ -674,6 +791,30 @@ dialog :link { color: rgb(255 255 255); } +/* Legacy dialogs do not use .dialog, so map their surface, type, and links to + Nova tokens. Keep line-height proportional to the tokenized font size. */ +/*#if MOZCENTRAL*/ +@media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + dialog:not(.dialog) { + padding: var(--space-large); + font-size: var(--font-size-small); + line-height: 150%; + border-spacing: var(--space-xsmall); + background-color: var(--background-color-canvas); + border: 1px solid var(--border-color); + box-shadow: var(--box-shadow-level-3); + + .separator { + margin-block: var(--space-small); + } + + :link { + color: var(--link-color); + } + } +} +/*#endif*/ + #passwordDialog { text-align: center; } @@ -846,6 +987,29 @@ dialog :link { height: auto; } } + + /* Use Nova's inset focus ring. The toggled-hover rule is !important, so pass + its ring through the variable. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--button-border-radius); + + &:focus-visible { + outline: var(--focus-outline); + outline-offset: var(--focus-outline-inset); + } + + &.toggled:hover:focus-visible { + --toggled-hover-btn-outline: var(--focus-outline); + } + + &.labeled { + border-radius: var(--panel-menuitem-border-radius); + gap: var(--space-small); + padding-inline-start: var(--space-small); + } + } + /*#endif*/ } .toolbarButtonWithContainer { @@ -862,6 +1026,14 @@ dialog :link { .menu { padding-block: 5px; + + /* Inset Nova menu rows from the panel edge. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + padding-block: var(--space-small); + padding-inline: var(--space-small); + } + /*#endif*/ } .menuContainer { @@ -915,6 +1087,13 @@ dialog :link { padding-inline: 10px; padding-block: 10px; + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + padding-inline: var(--space-medium); + padding-block: var(--space-medium); + } + /*#endif*/ + > .editorParamsSetter { min-height: 26px; display: flex; @@ -952,6 +1131,23 @@ dialog :link { &::-moz-range-thumb { background-color: white; } + + /* Use Nova slider colors. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + &::-moz-range-progress { + background-color: var(--color-accent-primary); + } + + &::-moz-range-track { + background-color: var(--border-color-interactive); + } + + &::-moz-range-thumb { + background-color: var(--color-accent-primary); + } + } + /*#endif*/ } } } @@ -1069,6 +1265,17 @@ dialog :link { flex-wrap: wrap; justify-content: flex-start; + /* Inset the bar without changing its height; PDFFindBar uses height to + detect wrapping. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --input-horizontal-padding: var(--space-medium); + + border-radius: var(--border-radius-medium); + padding-inline: var(--space-small); + } + /*#endif*/ + > * { height: var(--toolbar-height); padding: var(--findbar-padding); @@ -1106,7 +1313,20 @@ dialog :link { &[data-status="notFound"] { background-color: rgb(255 102 102); + + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + background-color: var(--background-color-critical); + } + /*#endif*/ } + + /* Use Nova's search-field radius. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--input-search-border-radius); + } + /*#endif*/ } } @@ -1123,6 +1343,14 @@ dialog :link { color: rgb(82 82 82); padding-block: 4px; + /* Replace the fixed gray pair with Nova theme tokens. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + background-color: var(--background-color-box-info); + color: var(--text-color-deemphasized); + } + /*#endif*/ + &:empty { display: none; } @@ -1238,6 +1466,15 @@ dialog :link { padding-inline: 4px; margin: 2px; border-radius: 2px; + + /* Use Nova's pill shape for toolbar labels and findbar toggles. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + padding-inline: var(--space-small); + border-radius: var(--button-border-radius); + } + /*#endif*/ + color: var(--main-color); font-size: 12px; line-height: 14px; @@ -1264,6 +1501,12 @@ dialog :link { justify-content: space-between; gap: 1px; box-sizing: border-box; + + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + gap: var(--space-xxsmall); + } + /*#endif*/ } .dropdownToolbarButton { @@ -1337,11 +1580,85 @@ dialog :link { &:is(:hover, :focus-visible, :active)::after { background-color: var(--toolbar-icon-hover-bg-color); } + + /* Mirror moz-select. Keep the inner select opaque because SelectParent uses + its computed background for the option popup; paint hover as a gradient so + background-color stays stable. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + overflow: hidden; + border: var(--button-border); + border-radius: var(--button-border-radius); + background-color: var(--button-background-color); + color: var(--button-text-color); + + &:is(:hover, :has(> select:open)) { + border-color: var(--border-color-interactive-hover); + background-color: var(--button-background-color-hover); + color: var(--button-text-color-hover); + } + + &:has(> select:focus-visible) { + outline: var(--focus-outline); + outline-offset: var(--focus-outline-offset); + } + + > select { + height: auto; + min-height: var(--button-min-height); + border-radius: inherit; + padding-block: var(--button-padding-block); + padding-inline: var(--button-padding-inline) + calc( + var(--button-padding-inline) + var(--icon-size) + var(--space-small) + ); + font-size: var(--font-size-root); + font-weight: var(--button-font-weight); + color: inherit; + background-color: var(--background-color-box); + + > option { + color: var(--text-color); + font-weight: var(--font-weight); + } + + &:is(:hover, :focus-visible) { + background-color: var(--background-color-box); + color: inherit; + } + + &:focus-visible { + outline: none; + } + } + + &:is(:hover, :has(> select:open)) > select { + background-image: linear-gradient( + var(--button-background-color-hover), + var(--button-background-color-hover) + ); + } + + &::after, + &:is(:hover, :focus-visible, :active)::after { + inset-inline-end: var(--button-padding-inline); + background-color: currentColor; + mask-image: var(--toolbarButton-menuArrowNova-icon); + } + } + /*#endif*/ } #toolbarContainer { --menuitem-height: calc(var(--toolbar-height) - 6px); + /* Keep menu rows at Nova's 32px size across toolbar densities. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --menuitem-height: var(--size-item-large); + } + /*#endif*/ + width: 100%; height: var(--toolbar-height); padding: var(--toolbar-vertical-padding) var(--toolbar-horizontal-padding); @@ -1374,6 +1691,13 @@ dialog :link { #toolbarViewerLeft { margin-inline-start: 8px; + /* --toolbar-horizontal-padding already insets the row. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + margin-inline-start: 0; + } + /*#endif*/ + #numPages.toolbarLabel { padding-inline-start: 3px; flex: none; diff --git a/web/views_manager.css b/web/views_manager.css index 4fa3e69e57377..1ad2c2bf14bc8 100644 --- a/web/views_manager.css +++ b/web/views_manager.css @@ -149,6 +149,32 @@ --multiple-dragging-indicator-bg: var(--indicator-color); --multiple-dragging-text-color: light-dark(#fbfbfe, #15141a); + /* Use opaque Nova surfaces and carry warning severity in the icon color. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --sidebar-bg-color: var(--background-color-box); + --sidebar-backdrop-filter: none; + --header-bg: var(--background-color-box); + --status-undo-bg: color-mix( + in srgb, + var(--color-accent-primary) 8%, + transparent + ); + --status-warning-bg: var(--background-color-box-info); + --indicator-warning-color: var(--icon-color-critical); + --image-border-color: var(--border-color-deemphasized); + --image-hover-border-color: var(--border-color-interactive); + --image-page-number-bg: var(--background-color-box-info); + --image-current-page-number-fg: var(--button-text-color-primary); + --image-dragging-placeholder-bg: color-mix( + in srgb, + var(--color-accent-primary) 8%, + transparent + ); + --multiple-dragging-bg: var(--background-color-box); + } + /*#endif*/ + @media screen and (forced-colors: active) { --views-text-color: var(--text-color, CanvasText); --button-fg: var(--button-text-color, ButtonText); @@ -263,6 +289,19 @@ } } + /* Radius the opaque header instead of clipping the panel, which would hide + the protruding resizer. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + padding-block-start: 0; + + #viewsManagerHeader { + border-start-start-radius: var(--panel-border-radius); + border-start-end-radius: var(--panel-border-radius); + } + } + /*#endif*/ + #viewsManagerHeader { display: flex; flex-direction: column; @@ -683,6 +722,12 @@ min-height: 24px; padding: 4px 16px; + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--button-border-radius); + } + /*#endif*/ + font: menu; font-size: 13px; font-style: normal; @@ -725,6 +770,11 @@ &:not(.isDragging) > .thumbnailImageContainer::after { content: attr(page-number); border-radius: 8px; + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--border-radius-circle); + } + /*#endif*/ border: 1px solid var(--image-page-number-border-color); background-color: var(--image-page-number-bg); color: var(--image-page-number-fg); @@ -777,6 +827,13 @@ user-select: none; position: relative; + /* Use Nova's medium card radius. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--border-radius-medium); + } + /*#endif*/ + img { width: 100%; height: 100%; @@ -785,6 +842,12 @@ outline: none; user-select: none; pointer-events: none; + + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--border-radius-medium); + } + /*#endif*/ } &.missingThumbnailImage { @@ -912,11 +975,26 @@ box-sizing: content-box; outline: none; user-select: none; + + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: var(--border-radius-medium); + } + /*#endif*/ } &::after { content: attr(data-multiple-count); border-radius: calc(8px * var(--thumbnail-dragging-scale)); + + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + border-radius: calc( + var(--border-radius-medium) * + var(--thumbnail-dragging-scale) + ); + } + /*#endif*/ background-color: var(--multiple-dragging-indicator-bg); color: var(--multiple-dragging-text-color); position: absolute; @@ -949,10 +1027,26 @@ rgb(255 255 255 / 0.9) ); + /* Match the Nova row colors used by the outline and layers views. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + --attachment-color: var(--text-color); + --attachment-hover-color: var(--text-color); + --attachment-bg-color: var(--toolbarbutton-background-color-hover); + } + /*#endif*/ + > ul { list-style-type: none; padding: 0; + /* Match the menu-item inset in tree.css. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + padding-inline: var(--panel-menuitem-margin-inline); + } + /*#endif*/ + > li > a { text-decoration: none; display: inline-block; @@ -976,6 +1070,21 @@ border-radius: 2px; color: var(--attachment-hover-color); } + + /* Repeat the radius for the more-specific :hover rule above. */ + /*#if MOZCENTRAL*/ + @media -moz-pref("pdfjs.enableNova") and -moz-pref("browser.nova.enabled") { + box-sizing: border-box; + min-width: 100%; + padding-block: var(--space-xsmall); + padding-inline: var(--space-small); + + &, + &:hover { + border-radius: var(--panel-menuitem-border-radius); + } + } + /*#endif*/ } } } From 263bc08ccad7256b3db275b2bf2bc8e099cddbd2 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Fri, 14 Aug 2026 18:06:22 +0200 Subject: [PATCH 3/5] Shorten some `Array.prototype.map()` callbacks In a few spots we can directly pass the function, and don't need to create an "intermediate" arrow function. --- src/core/fonts.js | 2 +- src/core/struct_tree.js | 2 +- src/core/xfa/xhtml.js | 2 +- src/display/annotation_layer.js | 4 ++-- test/integration/freetext_editor_spec.mjs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/fonts.js b/src/core/fonts.js index 58e07bceaad7f..3601ac67af88f 100644 --- a/src/core/fonts.js +++ b/src/core/fonts.js @@ -966,7 +966,7 @@ function createNameTable(name, proto) { proto[0][8] || "Unknown", // 8.Manufacturer proto[0][9] || "Unknown", // 9.Designer ]; - const stringsBytes = strings.map(s => stringToBytes(s)); + const stringsBytes = strings.map(stringToBytes); // Mac want 1-byte per character strings while Windows want // 2-bytes per character, so duplicate the names table diff --git a/src/core/struct_tree.js b/src/core/struct_tree.js index 480b5ca58aa96..444ed650c17bc 100644 --- a/src/core/struct_tree.js +++ b/src/core/struct_tree.js @@ -728,7 +728,7 @@ class StructElementNode { if (Array.isArray(headers)) { const ids = headers .filter(header => typeof header === "string") - .map(header => stringToPDFString(header)); + .map(stringToPDFString); if (ids.length > 0) { map.set("headers", ids); } diff --git a/src/core/xfa/xhtml.js b/src/core/xfa/xhtml.js index d0f1cf34950c4..614c26aecbab5 100644 --- a/src/core/xfa/xhtml.js +++ b/src/core/xfa/xhtml.js @@ -265,7 +265,7 @@ class XhtmlObject extends XmlObject { xfaFont.letterSpacing = getMeasurement(value); break; case "margin": - const values = value.split(/ \t/).map(x => getMeasurement(x)); + const values = value.split(/ \t/).map(getMeasurement); switch (values.length) { case 1: margin.top = diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index c265c67de58d1..35a6fb145eecb 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -654,8 +654,8 @@ class AnnotationElement { return; } - const [rectBlX, rectBlY, rectTrX, rectTrY] = this.data.rect.map(x => - Math.fround(x) + const [rectBlX, rectBlY, rectTrX, rectTrY] = this.data.rect.map( + Math.fround ); if (quadPoints.length === 8) { diff --git a/test/integration/freetext_editor_spec.mjs b/test/integration/freetext_editor_spec.mjs index ada2868e2d29f..35aaa2e72bd9b 100644 --- a/test/integration/freetext_editor_spec.mjs +++ b/test/integration/freetext_editor_spec.mjs @@ -867,7 +867,7 @@ describe("FreeText Editor", () => { }, proprName); const rects = (await serialize("rect")).map(rect => - rect.slice(0, 2).map(x => Math.floor(x)) + rect.slice(0, 2).map(Math.floor) ); const expected = [ [-28, 695], From 92b2cd331d73f3208d040aa974199b7b2a226ca8 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sat, 15 Aug 2026 09:47:59 +0200 Subject: [PATCH 4/5] Use `Array.prototype.filter(Boolean)` more, when filtering out truthy values There are some spots where we want to filter out any truthy values, where that's currently done with an arrow function. Those cases can instead use `Boolean` as the callback function, which is a pattern that newer parts of the code-base is already using. --- src/core/document.js | 2 +- src/core/editor/pdf_editor.js | 4 ++-- src/core/worker.js | 2 +- src/core/xfa/config.js | 2 +- src/core/xfa/template.js | 2 +- test/unit/display_utils_spec.js | 2 +- web/new_alt_text_manager.js | 2 +- web/pdf_find_controller.js | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/core/document.js b/src/core/document.js index 46ddce9c70b07..7e1adf0a68316 100644 --- a/src/core/document.js +++ b/src/core/document.js @@ -1980,7 +1980,7 @@ class PDFDocument { for (const [name, promises] of fieldPromises) { allPromises.push( Promise.all(promises).then(fields => { - fields = fields.filter(field => !!field); + fields = fields.filter(Boolean); if (fields.length > 0) { allFields.set(name, fields); } diff --git a/src/core/editor/pdf_editor.js b/src/core/editor/pdf_editor.js index 1bcf7c5736fea..7de6ab47e60f7 100644 --- a/src/core/editor/pdf_editor.js +++ b/src/core/editor/pdf_editor.js @@ -1291,7 +1291,7 @@ class PDFEditor { } await Promise.all(promises); - newAnnotations = newAnnotations.filter(annot => !!annot); + newAnnotations = newAnnotations.filter(Boolean); pageData.annotations = newAnnotations.length > 0 ? newAnnotations : null; pageData.documentData.hasSignatureAnnotations ||= hasSignatureAnnotations; } @@ -2377,7 +2377,7 @@ class PDFEditor { const numPages = document.numPages; const labelsByPageIndex = new Map(); const oldPageIndices = new Set( - this.oldPages.filter(p => !!p).map(({ page: { pageIndex } }) => pageIndex) + this.oldPages.filter(Boolean).map(({ page: { pageIndex } }) => pageIndex) ); let currentLabel = null; let stFirstIndex = -1; diff --git a/src/core/worker.js b/src/core/worker.js index 9324ef1b374e2..ddc9d0991de40 100644 --- a/src/core/worker.js +++ b/src/core/worker.js @@ -515,7 +515,7 @@ class WorkerMessageHandler { } await Promise.all(pagePromises); const annotations = await Promise.all(annotationPromises); - return annotations.filter(a => !!a); + return annotations.filter(Boolean); } finally { if (task) { finishWorkerTask(task); diff --git a/src/core/xfa/config.js b/src/core/xfa/config.js index 832933896ae1f..a6006608fa862 100644 --- a/src/core/xfa/config.js +++ b/src/core/xfa/config.js @@ -426,7 +426,7 @@ class EquateRange extends XFAObject { for (let range of unicodeRange .split(",") .map(x => x.trim()) - .filter(x => !!x)) { + .filter(Boolean)) { range = range.split("-", 2).map(x => { const found = x.match(unicodeRegex); if (!found) { diff --git a/src/core/xfa/template.js b/src/core/xfa/template.js index a9c52738670a9..9964cfd848a23 100644 --- a/src/core/xfa/template.js +++ b/src/core/xfa/template.js @@ -5703,7 +5703,7 @@ class Text extends ContentObject { if (typeof this[$content] === "string") { return this[$content] .split(/[\u2029\u2028\n]/) - .filter(line => !!line) + .filter(Boolean) .join("\n"); } return this[$content][$text](); diff --git a/test/unit/display_utils_spec.js b/test/unit/display_utils_spec.js index de8af9aeeb3a5..e5c2344a1fc15 100644 --- a/test/unit/display_utils_spec.js +++ b/test/unit/display_utils_spec.js @@ -407,7 +407,7 @@ describe("display_utils", function () { // Unlike other tests we cannot simply compare the HTML-strings since // Chrome and Firefox produce different results. Instead we compare sets // containing the individual parts of the HTML-strings. - const splitParts = s => new Set(s.split(/[<>/ ]+/).filter(x => x)); + const splitParts = s => new Set(s.split(/[<>/ ]+/).filter(Boolean)); it("should render plain text", function () { if (isNodeJS) { diff --git a/web/new_alt_text_manager.js b/web/new_alt_text_manager.js index 2b39e018b701c..edcfdd5a77515 100644 --- a/web/new_alt_text_manager.js +++ b/web/new_alt_text_manager.js @@ -498,7 +498,7 @@ class NewAltTextManager { text .toLowerCase() .split(/[^\p{L}\p{N}]+/gu) - .filter(x => !!x) + .filter(Boolean) ); } diff --git a/web/pdf_find_controller.js b/web/pdf_find_controller.js index 9315380762738..71345b91754cf 100644 --- a/web/pdf_find_controller.js +++ b/web/pdf_find_controller.js @@ -629,7 +629,7 @@ class PDFFindController { } // We don't bother caching the normalized search query in the Array-case, // since this code-path is *essentially* unused in the default viewer. - return (query || []).filter(q => !!q).map(q => normalize(q)[0]); + return (query || []).filter(Boolean).map(q => normalize(q)[0]); } #shouldDirtyMatch(state) { From 5350280057c9c7bfdb9a56dfe542f60eac015723 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sat, 15 Aug 2026 12:57:02 +0200 Subject: [PATCH 5/5] Modernize the `XRefMock` unit-test helper --- test/unit/name_number_tree_spec.js | 10 ++++---- test/unit/primitives_spec.js | 2 +- test/unit/test_utils.js | 38 ++++++++++++++++-------------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/test/unit/name_number_tree_spec.js b/test/unit/name_number_tree_spec.js index 3432c51101a8b..48e5103dbb8ce 100644 --- a/test/unit/name_number_tree_spec.js +++ b/test/unit/name_number_tree_spec.js @@ -21,7 +21,7 @@ import { XRefMock } from "./test_utils.js"; describe("NameOrNumberTree", function () { describe("NameTree", function () { it("should return an empty map when root is null", function () { - const xref = new XRefMock([]); + const xref = new XRefMock(); const tree = new NameTree(null, xref); expect(tree.getAll().size).toEqual(0); }); @@ -30,7 +30,7 @@ describe("NameOrNumberTree", function () { const root = new Dict(); root.set("Names", ["alpha", "value_a", "beta", "value_b"]); - const xref = new XRefMock([]); + const xref = new XRefMock(); const tree = new NameTree(root, xref); const map = tree.getAll(); @@ -66,7 +66,7 @@ describe("NameOrNumberTree", function () { const root = new Dict(); root.set("Kids", [inlineLeaf]); - const xref = new XRefMock([]); + const xref = new XRefMock(); const tree = new NameTree(root, xref); // Should not throw even though the kid is an inline Dict (not a Ref). @@ -126,7 +126,7 @@ describe("NameOrNumberTree", function () { const root = new Dict(); root.set("Nums", [1, "one", 2, "two"]); - const xref = new XRefMock([]); + const xref = new XRefMock(); const tree = new NumberTree(root, xref); const map = tree.getAll(); @@ -144,7 +144,7 @@ describe("NameOrNumberTree", function () { const root = new Dict(); root.set("Kids", [inlineLeaf]); - const xref = new XRefMock([]); + const xref = new XRefMock(); const tree = new NumberTree(root, xref); const map = tree.getAll(); diff --git a/test/unit/primitives_spec.js b/test/unit/primitives_spec.js index c047dfa615856..fb31e53f08da1 100644 --- a/test/unit/primitives_spec.js +++ b/test/unit/primitives_spec.js @@ -127,7 +127,7 @@ describe("primitives", function () { const dict = new Dict(null); expect(dict.xref).toBeNull(); - const xref = new XRefMock([]); + const xref = new XRefMock(); dict.assignXref(xref); expect(dict.xref).toEqual(xref); }); diff --git a/test/unit/test_utils.js b/test/unit/test_utils.js index cd1cbea0aa054..02e1e0bb92f9d 100644 --- a/test/unit/test_utils.js +++ b/test/unit/test_utils.js @@ -123,36 +123,38 @@ function getCrossOriginHostname(hostname) { } class XRefMock { - constructor(array) { - this._map = Object.create(null); - this._newTemporaryRefNum = null; - this._newPersistentRefNum = null; - this.stream = new NullStream(); - - for (const key in array) { - const obj = array[key]; - this._map[obj.ref.toString()] = obj.data; + #map = new Map(); + + #newPersistentRefNum = null; + + #newTemporaryRefNum = null; + + stream = new NullStream(); + + constructor(array = []) { + for (const { ref, data } of array) { + this.#map.set(ref.toString(), data); } } getNewPersistentRef(obj) { - if (this._newPersistentRefNum === null) { - this._newPersistentRefNum = Object.keys(this._map).length || 1; + if (this.#newPersistentRefNum === null) { + this.#newPersistentRefNum = this.#map.size || 1; } - const ref = Ref.get(this._newPersistentRefNum++, 0); - this._map[ref.toString()] = obj; + const ref = Ref.get(this.#newPersistentRefNum++, 0); + this.#map.set(ref.toString(), obj); return ref; } getNewTemporaryRef() { - if (this._newTemporaryRefNum === null) { - this._newTemporaryRefNum = Object.keys(this._map).length || 1; + if (this.#newTemporaryRefNum === null) { + this.#newTemporaryRefNum = this.#map.size || 1; } - return Ref.get(this._newTemporaryRefNum++, 0); + return Ref.get(this.#newTemporaryRefNum++, 0); } resetNewTemporaryRef() { - this._newTemporaryRefNum = null; + this.#newTemporaryRefNum = null; } countUpdatesAfter(offset) { @@ -160,7 +162,7 @@ class XRefMock { } fetch(ref) { - return this._map[ref.toString()]; + return this.#map.get(ref.toString()); } async fetchAsync(ref) {