diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 26924ed39..94e849728 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -30,8 +30,5 @@ jobs: - name: Run lint run: npx gulp lint - - name: Run lint-chromium - run: npx gulp lint-chromium - - name: Run lint-mozcentral run: npx gulp lint-mozcentral diff --git a/extensions/chromium/preferences_schema.json b/extensions/chromium/preferences_schema.json deleted file mode 100644 index 269d71e66..000000000 --- a/extensions/chromium/preferences_schema.json +++ /dev/null @@ -1,256 +0,0 @@ -{ - "type": "object", - "properties": { - "viewerCssTheme": { - "title": "Theme", - "description": "The theme to use.\n0 = Use system theme.\n1 = Light theme.\n2 = Dark theme.", - "type": "integer", - "enum": [0, 1, 2], - "default": 2 - }, - "showPreviousViewOnLoad": { - "description": "DEPRECATED. Set viewOnLoad to 1 to disable showing the last page/position on load.", - "type": "boolean", - "default": true - }, - "viewOnLoad": { - "title": "View position on load", - "description": "The position in the document upon load.\n -1 = Default (uses OpenAction if available, otherwise equal to `viewOnLoad = 0`).\n 0 = The last viewed page/position.\n 1 = The initial page/position.", - "type": "integer", - "enum": [-1, 0, 1], - "default": 0 - }, - "defaultZoomDelay": { - "title": "Default zoom delay", - "description": "Delay (in ms) to wait before redrawing the canvas.", - "type": "integer", - "default": 400 - }, - "defaultZoomValue": { - "title": "Default zoom level", - "description": "Default zoom level of the viewer. Accepted values: 'auto', 'page-actual', 'page-width', 'page-height', 'page-fit', or a zoom level in percents.", - "type": "string", - "pattern": "|auto|page-actual|page-width|page-height|page-fit|[0-9]+\\.?[0-9]*(,[0-9]+\\.?[0-9]*){0,2}", - "default": "" - }, - "sidebarViewOnLoad": { - "title": "Sidebar state on load", - "description": "Controls the state of the sidebar upon load.\n -1 = Default (uses PageMode if available, otherwise the last position if available/enabled).\n 0 = Do not show sidebar.\n 1 = Show thumbnails in sidebar.\n 2 = Show document outline in sidebar.\n 3 = Show attachments in sidebar.", - "type": "integer", - "enum": [-1, 0, 1, 2, 3], - "default": -1 - }, - "enableHandToolOnLoad": { - "description": "DEPRECATED. Set cursorToolOnLoad to 1 to enable the hand tool by default.", - "type": "boolean", - "default": false - }, - "enableHWA": { - "title": "Enable hardware acceleration", - "description": "Whether to enable hardware acceleration.", - "type": "boolean", - "default": true - }, - "enableAltText": { - "type": "boolean", - "default": false - }, - "enableGuessAltText": { - "type": "boolean", - "default": true - }, - "enableAltTextModelDownload": { - "type": "boolean", - "default": true - }, - "enableNewAltTextWhenAddingImage": { - "type": "boolean", - "default": true - }, - "altTextLearnMoreUrl": { - "type": "string", - "default": "" - }, - "commentLearnMoreUrl": { - "type": "string", - "default": "" - }, - "enableSignatureEditor": { - "type": "boolean", - "default": false - }, - "enableSplitMerge": { - "type": "boolean", - "default": false - }, - "enableUpdatedAddImage": { - "type": "boolean", - "default": false - }, - "cursorToolOnLoad": { - "title": "Cursor tool on load", - "description": "The cursor tool that is enabled upon load.\n 0 = Text selection tool.\n 1 = Hand tool.", - "type": "integer", - "enum": [0, 1], - "default": 0 - }, - "pdfBugEnabled": { - "title": "Enable debugging tools", - "description": "Whether to enable debugging tools.", - "type": "boolean", - "default": false - }, - "enableScripting": { - "title": "Enable active content (JavaScript) in PDFs", - "type": "boolean", - "description": "Whether to allow execution of active content (JavaScript) by PDF files.", - "default": false - }, - "enableHighlightFloatingButton": { - "type": "boolean", - "default": false - }, - "highlightEditorColors": { - "type": "string", - "default": "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F,yellow_HCM=#FFFFCC,green_HCM=#53FFBC,blue_HCM=#80EBFF,pink_HCM=#F6B8FF,red_HCM=#C50043" - }, - "disableRange": { - "title": "Disable range requests", - "description": "Whether to disable range requests (not recommended).", - "type": "boolean", - "default": false - }, - "disableStream": { - "title": "Disable streaming for requests", - "description": "Whether to disable streaming for requests (not recommended).", - "type": "boolean", - "default": false - }, - "disableAutoFetch": { - "type": "boolean", - "default": false - }, - "disableFontFace": { - "title": "Disable @font-face", - "description": "Whether to disable @font-face and fall back to canvas rendering (this is more resource-intensive).", - "type": "boolean", - "default": false - }, - "disableTextLayer": { - "description": "DEPRECATED. Set textLayerMode to 0 to disable the text selection layer by default.", - "type": "boolean", - "default": false - }, - "textLayerMode": { - "title": "Text layer mode", - "description": "Controls if the text layer is enabled, and the selection mode that is used.\n 0 = Disabled.\n 1 = Enabled.", - "type": "integer", - "enum": [0, 1], - "default": 1 - }, - "externalLinkTarget": { - "title": "External links target window", - "description": "Controls how external links will be opened.\n 0 = default.\n 1 = replaces current window.\n 2 = new window/tab.\n 3 = parent.\n 4 = in top window.", - "type": "integer", - "enum": [0, 1, 2, 3, 4], - "default": 0 - }, - "disablePageLabels": { - "type": "boolean", - "default": false - }, - "disablePageMode": { - "description": "DEPRECATED.", - "type": "boolean", - "default": false - }, - "disableTelemetry": { - "title": "Disable telemetry", - "type": "boolean", - "description": "Whether to prevent the extension from reporting the extension and browser version to the extension developers.", - "default": false - }, - "annotationMode": { - "type": "integer", - "enum": [0, 1, 2, 3], - "default": 2 - }, - "annotationEditorMode": { - "type": "integer", - "enum": [-1, 0, 3, 15], - "default": 0 - }, - "capCanvasAreaFactor": { - "type": "integer", - "default": 200 - }, - "enablePermissions": { - "type": "boolean", - "default": false - }, - "enableXfa": { - "type": "boolean", - "default": true - }, - "historyUpdateUrl": { - "type": "boolean", - "default": false - }, - "ignoreDestinationZoom": { - "title": "Ignore the zoom argument in destinations", - "description": "When enabled it will maintain the currently active zoom level, rather than letting the PDF document modify it, when navigating to internal destinations.", - "type": "boolean", - "default": false - }, - "enablePrintAutoRotate": { - "title": "Automatically rotate printed pages", - "description": "When enabled, landscape pages are rotated when printed.", - "type": "boolean", - "default": true - }, - "scrollModeOnLoad": { - "title": "Scroll mode on load", - "description": "Controls how the viewer scrolls upon load.\n -1 = Default (uses the last position if available/enabled).\n 3 = Page scrolling.\n 0 = Vertical scrolling.\n 1 = Horizontal scrolling.\n 2 = Wrapped scrolling.", - "type": "integer", - "enum": [-1, 0, 1, 2, 3], - "default": -1 - }, - "spreadModeOnLoad": { - "title": "Spread mode on load", - "description": "Whether the viewer should join pages into spreads upon load.\n -1 = Default (uses the last position if available/enabled).\n 0 = No spreads.\n 1 = Odd spreads.\n 2 = Even spreads.", - "type": "integer", - "enum": [-1, 0, 1, 2], - "default": -1 - }, - "forcePageColors": { - "description": "When enabled, the pdf rendering will use the high contrast mode colors", - "type": "boolean", - "default": false - }, - "pageColorsBackground": { - "description": "The color is a string as defined in CSS. Its goal is to help improve readability in high contrast mode", - "type": "string", - "default": "Canvas" - }, - "pageColorsForeground": { - "description": "The color is a string as defined in CSS. Its goal is to help improve readability in high contrast mode", - "type": "string", - "default": "CanvasText" - }, - "enableAutoLinking": { - "description": "Enable creation of hyperlinks from text that look like URLs.", - "type": "boolean", - "default": true - }, - "enableComment": { - "description": "Enable creation of comment annotations.", - "type": "boolean", - "default": false - }, - "enableOptimizedPartialRendering": { - "description": "Enable tracking of PDF operations to optimize partial rendering.", - "type": "boolean", - "default": false - } - } -} diff --git a/external/chromium/prefs.mjs b/external/chromium/prefs.mjs new file mode 100644 index 000000000..63e3b10e8 --- /dev/null +++ b/external/chromium/prefs.mjs @@ -0,0 +1,270 @@ +/* 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. + */ + +const prefsMetadata = { + annotationEditorMode: { + enum: [-1, 0, 3, 15], + }, + annotationMode: { + enum: [0, 1, 2, 3], + }, + cursorToolOnLoad: { + title: "Cursor tool on load", + description: + "The cursor tool that is enabled upon load.\n 0 = Text selection tool.\n 1 = Hand tool.", + enum: [0, 1], + }, + defaultZoomDelay: { + title: "Default zoom delay", + description: "Delay (in ms) to wait before redrawing the canvas.", + }, + defaultZoomValue: { + title: "Default zoom level", + description: + "Default zoom level of the viewer. Accepted values: 'auto', 'page-actual', 'page-width', 'page-height', 'page-fit', or a zoom level in percents.", + pattern: + "|auto|page-actual|page-width|page-height|page-fit|[0-9]+\\.?[0-9]*(,[0-9]+\\.?[0-9]*){0,2}", + }, + disableFontFace: { + title: "Disable @font-face", + description: + "Whether to disable @font-face and fall back to canvas rendering (this is more resource-intensive).", + }, + disableRange: { + title: "Disable range requests", + description: "Whether to disable range requests (not recommended).", + }, + disableStream: { + title: "Disable streaming for requests", + description: "Whether to disable streaming for requests (not recommended).", + }, + disableTelemetry: { + title: "Disable telemetry", + description: + "Whether to prevent the extension from reporting the extension and browser version to the extension developers.", + }, + enableAutoLinking: { + description: "Enable creation of hyperlinks from text that look like URLs.", + }, + enableComment: { + description: "Enable creation of comment annotations.", + }, + enableHWA: { + title: "Enable hardware acceleration", + description: "Whether to enable hardware acceleration.", + }, + enableOptimizedPartialRendering: { + description: + "Enable tracking of PDF operations to optimize partial rendering.", + }, + enablePrintAutoRotate: { + title: "Automatically rotate printed pages", + description: "When enabled, landscape pages are rotated when printed.", + }, + enableScripting: { + title: "Enable active content (JavaScript) in PDFs", + description: + "Whether to allow execution of active content (JavaScript) by PDF files.", + }, + externalLinkTarget: { + title: "External links target window", + description: + "Controls how external links will be opened.\n 0 = default.\n 1 = replaces current window.\n 2 = new window/tab.\n 3 = parent.\n 4 = in top window.", + enum: [0, 1, 2, 3, 4], + }, + forcePageColors: { + description: + "When enabled, the pdf rendering will use the high contrast mode colors", + }, + ignoreDestinationZoom: { + title: "Ignore the zoom argument in destinations", + description: + "When enabled it will maintain the currently active zoom level, rather than letting the PDF document modify it, when navigating to internal destinations.", + }, + pageColorsBackground: { + description: + "The color is a string as defined in CSS. Its goal is to help improve readability in high contrast mode", + }, + pageColorsForeground: { + description: + "The color is a string as defined in CSS. Its goal is to help improve readability in high contrast mode", + }, + pdfBugEnabled: { + title: "Enable debugging tools", + description: "Whether to enable debugging tools.", + }, + scrollModeOnLoad: { + title: "Scroll mode on load", + description: + "Controls how the viewer scrolls upon load.\n -1 = Default (uses the last position if available/enabled).\n 0 = Vertical scrolling.\n 1 = Horizontal scrolling.\n 2 = Wrapped scrolling.\n 3 = Page scrolling.", + enum: [-1, 0, 1, 2, 3], + }, + sidebarViewOnLoad: { + title: "Sidebar state on load", + description: + "Controls the state of the sidebar upon load.\n -1 = Default (uses PageMode if available, otherwise the last position if available/enabled).\n 0 = Do not show sidebar.\n 1 = Show thumbnails in sidebar.\n 2 = Show document outline in sidebar.\n 3 = Show attachments in sidebar.", + enum: [-1, 0, 1, 2, 3], + }, + spreadModeOnLoad: { + title: "Spread mode on load", + description: + "Whether the viewer should join pages into spreads upon load.\n -1 = Default (uses the last position if available/enabled).\n 0 = No spreads.\n 1 = Odd spreads.\n 2 = Even spreads.", + enum: [-1, 0, 1, 2], + }, + textLayerMode: { + title: "Text layer mode", + description: + "Controls if the text layer is enabled, and the selection mode that is used.\n 0 = Disabled.\n 1 = Enabled.", + enum: [0, 1], + }, + viewerCssTheme: { + title: "Theme", + description: + "The theme to use.\n0 = Use system theme.\n1 = Light theme.\n2 = Dark theme.", + enum: [0, 1, 2], + }, + viewOnLoad: { + title: "View position on load", + description: + "The position in the document upon load.\n -1 = Default (uses OpenAction if available, otherwise equal to `viewOnLoad = 0`).\n 0 = The last viewed page/position.\n 1 = The initial page/position.", + enum: [-1, 0, 1], + }, +}; + +// Deprecated keys are allowed in the managed preferences file. +// The code maintainer is responsible for adding migration logic to +// extensions/chromium/options/migration.js and web/chromecom.js . +const deprecatedPrefs = { + disablePageMode: { + description: "DEPRECATED.", + type: "boolean", + default: false, + }, + disableTextLayer: { + description: + "DEPRECATED. Set textLayerMode to 0 to disable the text selection layer by default.", + type: "boolean", + default: false, + }, + enableHandToolOnLoad: { + description: + "DEPRECATED. Set cursorToolOnLoad to 1 to enable the hand tool by default.", + type: "boolean", + default: false, + }, + showPreviousViewOnLoad: { + description: + "DEPRECATED. Set viewOnLoad to 1 to disable showing the last page/position on load.", + type: "boolean", + default: true, + }, +}; + +function buildPrefsSchema(prefs) { + const properties = Object.create(null); + + for (const name in prefs) { + const pref = prefs[name]; + let type = typeof pref; + + switch (type) { + case "boolean": + case "string": + break; + case "number": + type = "integer"; + break; + default: + throw new Error(`Invalid type (${type}) for "${name}"-preference.`); + } + + const metadata = prefsMetadata[name]; + if (metadata) { + let numMetadataKeys = 0; + // Do some (very basic) validation of the metadata. + for (const key in metadata) { + const entry = metadata[key]; + + switch (key) { + case "default": + case "type": + throw new Error( + `Invalid key (${key}) in metadata for "${name}"-preference.` + ); + case "description": + if (entry.startsWith("DEPRECATED.")) { + throw new Error( + `The \`description\` of the "${name}"-preference cannot begin with "DEPRECATED."` + ); + } + break; + } + numMetadataKeys++; + } + if (numMetadataKeys === 0) { + throw new Error( + `No metadata for "${name}"-preference, remove the entry.` + ); + } + } + + properties[name] = { + type, + default: pref, + ...metadata, + }; + } + + for (const name in prefsMetadata) { + if (!properties[name]) { + // Do *not* throw here, since keeping the metadata up-to-date should be + // the responsibility of the CHROMIUM-addon maintainer. + console.error( + `The "${name}"-preference was removed, add it to \`deprecatedPrefs\` instead.\n` + ); + } + } + + for (const name in deprecatedPrefs) { + const entry = deprecatedPrefs[name]; + + if (properties[name]) { + throw new Error( + `The "${name}"-preference should not be listed as deprecated.` + ); + } + if (!entry.description?.startsWith("DEPRECATED.")) { + throw new Error( + `The \`description\` of the deprecated "${name}"-preference must begin with "DEPRECATED."` + ); + } + for (const key of ["default", "type"]) { + if (key in entry) { + continue; + } + throw new Error( + `A \`${key}\` entry must be provided for the deprecated "${name}"-preference.` + ); + } + properties[name] = entry; + } + + return { + type: "object", + properties, + }; +} + +export { buildPrefsSchema }; diff --git a/gulpfile.mjs b/gulpfile.mjs index 3ad321ee3..b5c333617 100644 --- a/gulpfile.mjs +++ b/gulpfile.mjs @@ -20,6 +20,7 @@ import { import { exec, execSync, spawn, spawnSync } from "child_process"; import autoprefixer from "autoprefixer"; import babel from "@babel/core"; +import { buildPrefsSchema } from "./external/chromium/prefs.mjs"; import crypto from "crypto"; import fs from "fs"; import gulp from "gulp"; @@ -452,51 +453,6 @@ function getVersionJSON() { return JSON.parse(fs.readFileSync(BUILD_DIR + "version.json").toString()); } -function checkChromePreferencesFile(chromePrefsPath, webPrefs) { - const chromePrefs = JSON.parse(fs.readFileSync(chromePrefsPath).toString()); - const chromePrefsKeys = Object.keys(chromePrefs.properties).filter(key => { - const description = chromePrefs.properties[key].description; - // Deprecated keys are allowed in the managed preferences file. - // The code maintainer is responsible for adding migration logic to - // extensions/chromium/options/migration.js and web/chromecom.js . - return !description?.startsWith("DEPRECATED."); - }); - - let ret = true; - // Verify that every entry in webPrefs is also in preferences_schema.json. - for (const [key, value] of Object.entries(webPrefs)) { - if (!chromePrefsKeys.includes(key)) { - // Note: this would also reject keys that are present but marked as - // DEPRECATED. A key should not be marked as DEPRECATED if it is still - // listed in webPrefs. - ret = false; - console.log( - `Warning: ${chromePrefsPath} does not contain an entry for pref: ${key}` - ); - } else if (chromePrefs.properties[key].default !== value) { - ret = false; - console.log( - `Warning: not the same values (for "${key}"): ` + - `${chromePrefs.properties[key].default} !== ${value}` - ); - } - } - - // Verify that preferences_schema.json does not contain entries that are not - // in webPrefs (app_options.js). - for (const key of chromePrefsKeys) { - if (!(key in webPrefs)) { - ret = false; - console.log( - `Warning: ${chromePrefsPath} contains an unrecognized pref: ${key}. ` + - `Remove it, or prepend "DEPRECATED. " and add migration logic to ` + - `extensions/chromium/options/migration.js and web/chromecom.js.` - ); - } - } - return ret; -} - function createMainBundle(defines) { const mainFileConfig = createWebpackConfig(defines, { filename: defines.MINIFIED ? "pdf.min.mjs" : "pdf.mjs", @@ -1490,6 +1446,16 @@ gulp.task( ) ); +function createChromiumPrefsSchema() { + const prefs = getDefaultPreferences("chromium/"); + const chromiumPrefs = buildPrefsSchema(prefs); + + return createStringSource( + "preferences_schema.json", + JSON.stringify(chromiumPrefs, null, 2) + ); +} + gulp.task( "chromium", gulp.series( @@ -1582,14 +1548,12 @@ gulp.task( .pipe(replace(/\bPDFJSSCRIPT_VERSION\b/g, version)) .pipe(gulp.dest(CHROME_BUILD_DIR)), gulp - .src( - [ - "extensions/chromium/**/*.{html,js,css,png}", - "extensions/chromium/preferences_schema.json", - ], - { base: "extensions/chromium/", encoding: false } - ) + .src(["extensions/chromium/**/*.{html,js,css,png}"], { + base: "extensions/chromium/", + encoding: false, + }) .pipe(gulp.dest(CHROME_BUILD_DIR)), + createChromiumPrefsSchema().pipe(gulp.dest(CHROME_BUILD_DIR)), ]); } ) @@ -2124,39 +2088,6 @@ gulp.task( }) ); -gulp.task( - "lint-chromium", - gulp.series( - function scriptingLintChromium() { - const defines = { - ...DEFINES, - CHROME: true, - SKIP_BABEL: false, - TESTING: false, - }; - return buildDefaultPreferences(defines, "lint-chromium/"); - }, - async function prefsLintChromium() { - await parseDefaultPreferences("lint-chromium/"); - }, - function runLintChromium(done) { - console.log(); - console.log("### Checking supplemental Chromium files"); - - if ( - !checkChromePreferencesFile( - "extensions/chromium/preferences_schema.json", - getDefaultPreferences("lint-chromium/") - ) - ) { - done(new Error("chromium/preferences_schema is not in sync.")); - return; - } - done(); - } - ) -); - gulp.task("dev-wasm", function () { const VIEWER_WASM_OUTPUT = "web/wasm/";