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/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/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/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..d3a19abf2500f 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({
@@ -728,7 +733,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);
}
@@ -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/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/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..9d43c57e24a3f 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) {
@@ -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/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/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/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],
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/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/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/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/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) {
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/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) {
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/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,
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*/
}
}
}