diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index a1279de754437..0000000000000 --- a/.eslintignore +++ /dev/null @@ -1,13 +0,0 @@ -build/ -l10n/ -docs/ -node_modules/ -external/bcmaps/ -external/builder/fixtures/ -external/builder/fixtures_babel/ -external/quickjs/ -external/openjpeg/ -test/tmp/ -test/pdfs/ -web/locale/ -*~/ diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 77433e7c99ad0..0000000000000 --- a/.eslintrc +++ /dev/null @@ -1,267 +0,0 @@ -{ - "parserOptions": { - "ecmaVersion": 2022, - "sourceType": "module", - }, - - "plugins": [ - "import", - "json", - "mozilla", - "no-unsanitized", - "sort-exports", - "unicorn", - ], - - "extends": [ - "plugin:json/recommended", - "plugin:prettier/recommended" - ], - - "env": { - "browser": true, - "es2022": true, - "worker": true, - }, - - "globals": { - "PDFJSDev": "readonly", - "__non_webpack_import__": "readonly", - }, - - "rules": { - // Plugins - "import/export": "error", - "import/exports-last": "error", - "import/extensions": ["error", "always", { "ignorePackages": true, }], - "import/first": "error", - "import/named": "error", - "import/no-cycle": "error", - "import/no-empty-named-blocks": "error", - "import/no-commonjs": "error", - "import/no-mutable-exports": "error", - "import/no-self-import": "error", - "import/no-unresolved": ["error", { - "ignore": ["display", "pdfjs", "pdfjs-lib", "pdfjs-web", "web", "fluent-bundle", "fluent-dom"], - }], - "mozilla/avoid-removeChild": "error", - "mozilla/use-includes-instead-of-indexOf": "error", - "no-unsanitized/method": "error", - "no-unsanitized/property": "error", - "sort-exports/sort-exports": ["error", { - "ignoreCase": true, - }], - "unicorn/no-abusive-eslint-disable": "error", - "unicorn/no-array-push-push": "error", - "unicorn/no-instanceof-array": "error", - "unicorn/no-invalid-remove-event-listener": "error", - "unicorn/no-new-buffer": "error", - "unicorn/no-typeof-undefined": ["error", { - "checkGlobalVariables": false, - }], - "unicorn/no-useless-promise-resolve-reject": "error", - "unicorn/no-useless-spread": "error", - "unicorn/prefer-array-find": "error", - "unicorn/prefer-array-flat": "error", - "unicorn/prefer-array-flat-map": "error", - "unicorn/prefer-array-index-of": "error", - "unicorn/prefer-array-some": "error", - "unicorn/prefer-at": "error", - "unicorn/prefer-date-now": "error", - "unicorn/prefer-dom-node-append": "error", - "unicorn/prefer-dom-node-remove": "error", - "unicorn/prefer-logical-operator-over-ternary": "error", - "unicorn/prefer-modern-dom-apis": "error", - "unicorn/prefer-modern-math-apis": "error", - "unicorn/prefer-negative-index": "error", - "unicorn/prefer-optional-catch-binding": "error", - "unicorn/prefer-regexp-test": "error", - "unicorn/prefer-string-replace-all": "error", - "unicorn/prefer-string-starts-ends-with": "error", - "unicorn/prefer-ternary": ["error", "only-single-line"], - - // Possible errors - "for-direction": "error", - "getter-return": "error", - "no-async-promise-executor": "error", - "no-cond-assign": ["error", "except-parens"], - "no-constant-condition": ["error", { "checkLoops": false, }], - "no-debugger": "error", - "no-dupe-args": "error", - "no-dupe-else-if": "error", - "no-dupe-keys": "error", - "no-duplicate-case": "error", - "no-empty": ["error", { "allowEmptyCatch": true, }], - "no-empty-character-class": "error", - "no-ex-assign": "error", - "no-extra-boolean-cast": "error", - "no-func-assign": "error", - "no-inner-declarations": ["error", "functions"], - "no-invalid-regexp": "error", - "no-irregular-whitespace": "error", - "no-loss-of-precision": "error", - "no-obj-calls": "error", - "no-promise-executor-return": "error", - "no-regex-spaces": "error", - "no-setter-return": "error", - "no-sparse-arrays": "error", - "no-template-curly-in-string": "error", - "no-unexpected-multiline": "error", - "no-unreachable": "error", - "no-unsafe-finally": "error", - "no-unsafe-negation": "error", - "no-unsafe-optional-chaining": ["error", { "disallowArithmeticOperators": true }], - "no-unused-private-class-members": "error", - "use-isnan": ["error", { "enforceForIndexOf": true, }], - "valid-typeof": ["error", { "requireStringLiterals": true, }], - - // Best Practices - "accessor-pairs": ["error", { - "setWithoutGet": true, - "enforceForClassMembers": true, - }], - "consistent-return": "error", - "curly": ["error", "all"], - "default-case-last": "error", - "dot-notation": "error", - "eqeqeq": ["error", "always"], - "grouped-accessor-pairs": ["error", "getBeforeSet"], - "no-alert": "error", - "no-caller": "error", - "no-else-return": "error", - "no-empty-pattern": "error", - "no-eval": "error", - "no-extend-native": "error", - "no-extra-bind": "error", - "no-extra-label": "error", - "no-fallthrough": "error", - "no-floating-decimal": "error", - "no-global-assign": "error", - "no-implied-eval": "error", - "no-iterator": "error", - "no-lone-blocks": "error", - "no-lonely-if": "error", - "no-multi-str": "error", - "no-new": "error", - "no-new-func": "error", - "no-new-symbol": "error", - "no-new-wrappers": "error", - "no-octal-escape": "error", - "no-octal": "error", - "no-redeclare": "error", - "no-return-await": "error", - "no-self-assign": "error", - "no-self-compare": "error", - "no-throw-literal": "error", - "no-unused-expressions": "error", - "no-unused-labels": "error", - "no-useless-call": "error", - "no-useless-catch": "error", - "no-useless-concat": "error", - "no-useless-escape": "error", - "no-useless-return": "error", - "prefer-promise-reject-errors": "error", - "prefer-spread": "error", - "wrap-iife": ["error", "any"], - "yoda": ["error", "never", { - "exceptRange": true, - }], - - // Strict Mode - "strict": ["off", "global"], - - // Variables - "no-delete-var": "error", - "no-label-var": "error", - "no-shadow": "error", - "no-shadow-restricted-names": "error", - "no-undef-init": "error", - "no-undef": ["error", { "typeof": true, }], - "no-unused-vars": ["error", { - "vars": "all", - "args": "none", - }], - "no-use-before-define": ["error", { - "functions": false, - "classes": false, - "variables": false, - }], - - // Stylistic Issues - "lines-between-class-members": ["error", "always"], - "max-len": ["error", { - "code": 1000, - "comments": 80, - "ignoreUrls": true - }], - "new-cap": ["error", { "newIsCap": true, "capIsNew": false, }], - "no-array-constructor": "error", - "no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 0, "maxBOF": 1, }], - "no-nested-ternary": "error", - "no-new-object": "error", - "no-restricted-syntax": ["error", - { - "selector": "BinaryExpression[operator='instanceof'][right.name='Object']", - "message": "Use `typeof` rather than `instanceof Object`.", - }, - { - "selector": "CallExpression[callee.name='assert'][arguments.length!=2]", - "message": "`assert()` must always be invoked with two arguments.", - }, - { - "selector": "CallExpression[callee.name='isCmd'][arguments.length<2]", - "message": "Use `instanceof Cmd` rather than `isCmd()` with one argument.", - }, - { - "selector": "CallExpression[callee.name='isDict'][arguments.length<2]", - "message": "Use `instanceof Dict` rather than `isDict()` with one argument.", - }, - { - "selector": "CallExpression[callee.name='isName'][arguments.length<2]", - "message": "Use `instanceof Name` rather than `isName()` with one argument.", - }, - { - "selector": "NewExpression[callee.name='Cmd']", - "message": "Use `Cmd.get()` rather than `new Cmd()`.", - }, - { - "selector": "NewExpression[callee.name='Name']", - "message": "Use `Name.get()` rather than `new Name()`.", - }, - { - "selector": "NewExpression[callee.name='Ref']", - "message": "Use `Ref.get()` rather than `new Ref()`.", - }, - ], - "no-unneeded-ternary": "error", - "operator-assignment": "error", - "prefer-exponentiation-operator": "error", - "spaced-comment": ["error", "always", { - "block": { - "balanced": true, - } - }], - - // ECMAScript 6 - "arrow-body-style": ["error", "as-needed"], - "constructor-super": "error", - "no-class-assign": "error", - "no-const-assign": "error", - "no-dupe-class-members": "error", - "no-duplicate-imports": "error", - "no-this-before-super": "error", - "no-useless-computed-key": "error", - "no-useless-constructor": "error", - "no-useless-rename": "error", - "no-var": "error", - "object-shorthand": ["error", "always", { - "avoidQuotes": true, - }], - "prefer-const": "error", - "require-yield": "error", - "sort-imports": ["error", { - "ignoreCase": true, - }], - "template-curly-spacing": ["error", "never"], - }, -} diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 9a55ac44e1f40..9d1dbce8fc314 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -5,6 +5,8 @@ body: - type: textarea attributes: label: Attach (recommended) or Link to PDF file + description: Without this information the issue may be closed without comment + placeholder: Please place only the PDF file in this field validations: required: true @@ -14,6 +16,7 @@ body: - type: input attributes: label: Web browser and its version + description: Please ensure that it's supported, refer to [the FAQ](https://github.com/mozilla/pdf.js/wiki/Frequently-Asked-Questions#faq-support) validations: required: true - type: input @@ -24,11 +27,13 @@ body: - type: input attributes: label: PDF.js version + description: Please find official releases [here](https://github.com/mozilla/pdf.js/releases) validations: required: true - type: dropdown attributes: label: Is the bug present in the latest PDF.js version? + description: Please check the [online demo](https://github.com/mozilla/pdf.js#online-demo) options: ["Yes", "No"] default: 0 validations: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 39ba6a835d228..31eae7c1ec07c 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,4 @@ -blank_issues_enabled: true +blank_issues_enabled: false contact_links: - name: Need help? url: https://github.com/mozilla/pdf.js/discussions diff --git a/.github/fluent_linter_config.yml b/.github/fluent_linter_config.yml index a03205f445dbf..dd86299502dc7 100644 --- a/.github/fluent_linter_config.yml +++ b/.github/fluent_linter_config.yml @@ -25,3 +25,5 @@ CO01: exclusions: files: [] messages: [] +VC: + disabled: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c48286c92ad41..6cb1b4f383789 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,16 +11,16 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18, lts/*, 21] + node-version: [20, 22, 24] steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: ${{ matrix.node-version }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a871da8d8d12d..e09aabca02246 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/.github/workflows/fluent_linter.yml b/.github/workflows/fluent_linter.yml index e3137415c8263..f4a3a2b619114 100644 --- a/.github/workflows/fluent_linter.yml +++ b/.github/workflows/fluent_linter.yml @@ -25,12 +25,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - - name: Use Python 3.12 - uses: actions/setup-python@v5 + - name: Use Python 3.13 + uses: actions/setup-python@v6 with: - python-version: '3.12' + python-version: '3.13' cache: 'pip' - name: Install Fluent dependencies diff --git a/.github/workflows/font_tests.yml b/.github/workflows/font_tests.yml index ff8353b371ae8..8968aa5179750 100644 --- a/.github/workflows/font_tests.yml +++ b/.github/workflows/font_tests.yml @@ -36,22 +36,22 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: ${{ matrix.node-version }} - name: Install dependencies run: npm ci - - name: Use Python 3.12 - uses: actions/setup-python@v5 + - name: Use Python 3.13 + uses: actions/setup-python@v6 with: - python-version: '3.12' + python-version: '3.13' cache: 'pip' - name: Install Fonttools diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 99b224c6ab9d3..7e83745e42c88 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,12 +15,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: ${{ matrix.node-version }} diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 9672c615a4a33..df687d1dd2240 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -17,12 +17,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: ${{ matrix.node-version }} registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/publish_website.yml b/.github/workflows/publish_website.yml index 3e51ebff5c3fe..762d390062ea0 100644 --- a/.github/workflows/publish_website.yml +++ b/.github/workflows/publish_website.yml @@ -17,12 +17,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: ${{ matrix.node-version }} diff --git a/.github/workflows/types_tests.yml b/.github/workflows/types_tests.yml index f0b96a3be1a1f..a26d8c69b0ae9 100644 --- a/.github/workflows/types_tests.yml +++ b/.github/workflows/types_tests.yml @@ -15,12 +15,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: ${{ matrix.node-version }} diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile deleted file mode 100644 index 43355323f071a..0000000000000 --- a/.gitpod.Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM gitpod/workspace-full-vnc - -USER gitpod - -RUN sudo apt-get update && \ - sudo apt-get install -yq firefox && \ - sudo rm -rf /var/lib/apt/lists/* diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index 648e81e3f5a08..0000000000000 --- a/.gitpod.yml +++ /dev/null @@ -1,13 +0,0 @@ -image: - file: .gitpod.Dockerfile -tasks: - - command: | - gp await-port 8888 && gp preview $(gp url 8888)/web/viewer.html && echo '[{"name": "Firefox","path": "/usr/bin/firefox"}]' | jq '.' > test/resources/browser_manifests/browser_manifest.json - - - init: npm install -g gulp-cli && npm install - command: gulp server -ports: - - port: 8888 - onOpen: ignore - - port: 6080 - onOpen: ignore diff --git a/.prettierignore b/.prettierignore index e33594bd7c3f8..dded2e6c7d4bf 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,7 +5,10 @@ node_modules/ external/bcmaps/ external/builder/fixtures/ external/builder/fixtures_babel/ +external/openjpeg/ +external/qcms/ external/quickjs/ +test/stats/results/ test/tmp/ test/pdfs/ web/locale/ diff --git a/.puppeteerrc b/.puppeteerrc new file mode 100644 index 0000000000000..4457c47d724e4 --- /dev/null +++ b/.puppeteerrc @@ -0,0 +1,9 @@ +{ + "chrome": { + "skipDownload": false + }, + "firefox": { + "skipDownload": false, + "version": "nightly" + } +} diff --git a/.stylelintignore b/.stylelintignore index e33594bd7c3f8..90371a11625bd 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -6,6 +6,7 @@ external/bcmaps/ external/builder/fixtures/ external/builder/fixtures_babel/ external/quickjs/ +test/stats/results/ test/tmp/ test/pdfs/ web/locale/ diff --git a/.svglintrc.js b/.svglintrc.js new file mode 100644 index 0000000000000..68cf2ce57b64e --- /dev/null +++ b/.svglintrc.js @@ -0,0 +1,35 @@ +export default { + rules: { + valid: true, + + custom: [ + (reporter, $, ast, { filename }) => { + reporter.name = "no-svg-fill-context-fill"; + + const svg = $.find("svg"); + const fill = svg.attr("fill"); + if (fill === "context-fill") { + reporter.error( + "Fill attribute on svg element must not be set to 'context-fill'", + svg[0], + ast + ); + } + }, + ], + }, + ignore: [ + "build/**", + "l10n/**", + "docs/**", + "node_modules/**", + "external/bcmaps/**", + "external/builder/fixtures/**", + "external/builder/fixtures_babel/**", + "external/quickjs/**", + "test/tmp/**", + "test/pdfs/**", + "web/locale/**", + "*~/**", + ], +}; diff --git a/AUTHORS b/AUTHORS index b3181d2bc4b1b..5cdf633cae956 100644 --- a/AUTHORS +++ b/AUTHORS @@ -8,16 +8,21 @@ Andreas Gal Artur Adib Brendan Dahl Bill Walker +Calixte Denizet Chris G Jones David Quintana +Emily Wachowiak Felix Kälberer <@fkaelberer> Jakob Miland Jonas Jenwald Julian Viereck Justin D'Arcangelo Kalervo Kujala +Marco Castelluccio +Marie-Lilas Onanga Ozavino Michał Gołębiowski-Owczarek Ophir Lojkine <@lovasoa> +Ryan Casey Rob Wu Shaon Barman Sehyun Park diff --git a/README.md b/README.md index d47ff5102d309..b95186d4280eb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PDF.js [![Build Status](https://github.com/mozilla/pdf.js/workflows/CI/badge.svg?branch=master)](https://github.com/mozilla/pdf.js/actions?query=workflow%3ACI+branch%3Amaster) +# PDF.js [![CI](https://github.com/mozilla/pdf.js/actions/workflows/ci.yml/badge.svg?query=branch%3Amaster)](https://github.com/mozilla/pdf.js/actions/workflows/ci.yml?query=branch%3Amaster) [PDF.js](https://mozilla.github.io/pdf.js/) is a Portable Document Format (PDF) viewer that is built with HTML5. @@ -14,7 +14,7 @@ get involved, visit: + [Issue Reporting Guide](https://github.com/mozilla/pdf.js/blob/master/.github/CONTRIBUTING.md) + [Code Contribution Guide](https://github.com/mozilla/pdf.js/wiki/Contributing) + [Frequently Asked Questions](https://github.com/mozilla/pdf.js/wiki/Frequently-Asked-Questions) -+ [Good Beginner Bugs](https://github.com/mozilla/pdf.js/issues?direction=desc&labels=good-beginner-bug&page=1&sort=created&state=open) ++ [Good Beginner Bugs](https://github.com/mozilla/pdf.js/issues?q=is%3Aissue%20state%3Aopen%20label%3Agood-beginner-bug) + [Projects](https://github.com/mozilla/pdf.js/projects) Feel free to stop by our [Matrix room](https://chat.mozilla.org/#/room/#pdfjs:mozilla.org) for questions or guidance. @@ -57,9 +57,6 @@ all dependencies for PDF.js: $ npm install -> [!NOTE] -> On MacOS M1/M2 you may see some `node-gyp`-related errors when running `npm install`. This is because one of our dependencies, `"canvas"`, does not provide pre-built binaries for this platform and instead `npm` will try to build it from source. Please make sure to first install the necessary native dependencies using `brew`: https://github.com/Automattic/node-canvas#compiling. - Finally, you need to start a local web server as some browsers do not allow opening PDF files using a `file://` URL. Run: @@ -93,7 +90,7 @@ be loaded by `pdf.js`. The PDF.js files are large and should be minified for pro ## Using PDF.js in a web application To use PDF.js in a web application you can choose to use a pre-built version of the library -or to build it from source. We supply pre-built versions for usage with NPM and Bower under +or to build it from source. We supply pre-built versions for usage with NPM under the `pdfjs-dist` name. For more information and examples please refer to the [wiki page](https://github.com/mozilla/pdf.js/wiki/Setup-pdf.js-in-a-website) on this subject. @@ -137,4 +134,4 @@ Talk to us on Matrix: File an issue: -+ https://github.com/mozilla/pdf.js/issues/new ++ https://github.com/mozilla/pdf.js/issues/new/choose diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000000000..277475fad3b4d --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,473 @@ +import globals from "globals"; + +import import_ from "eslint-plugin-import"; +import jasmine from "eslint-plugin-jasmine"; +import json from "eslint-plugin-json"; +import noUnsanitized from "eslint-plugin-no-unsanitized"; +import perfectionist from "eslint-plugin-perfectionist"; +import prettierRecommended from "eslint-plugin-prettier/recommended"; +import unicorn from "eslint-plugin-unicorn"; + +const jsFiles = folder => { + const prefix = folder === "." ? "" : folder + "/"; + return [prefix + "**/*.js", prefix + "**/*.jsm", prefix + "**/*.mjs"]; +}; + +// Include all files referenced in extensions/chromium/background.js +const chromiumExtensionServiceWorkerFiles = [ + "extensions/chromium/extension-router.js", + "extensions/chromium/options/migration.js", + "extensions/chromium/pdfHandler.js", + "extensions/chromium/preserve-referer.js", + "extensions/chromium/suppress-update.js", + "extensions/chromium/telemetry.js", +]; + +export default [ + { + ignores: [ + "**/build/", + "**/l10n/", + "**/docs/", + "**/node_modules/", + "external/bcmaps/", + "external/builder/fixtures/", + "external/builder/fixtures_babel/", + "external/openjpeg/", + "external/qcms/", + "external/quickjs/", + "test/stats/results/", + "test/tmp/", + "test/pdfs/", + "web/locale/", + "web/wasm/", + "**/*~/", + ], + }, + + /* ======================================================================== *\ + Base configuration + \* ======================================================================== */ + + prettierRecommended, + { + files: ["**/*.json"], + ...json.configs.recommended, + }, + { + files: jsFiles("."), + ignores: chromiumExtensionServiceWorkerFiles, + languageOptions: { + globals: globals.browser, + }, + }, + { + files: jsFiles("."), + + plugins: { + import: import_.flatConfigs.recommended.plugins.import, + json, + "no-unsanitized": noUnsanitized, + perfectionist, + unicorn, + }, + + languageOptions: { + globals: { + ...globals.worker, + PDFJSDev: "readonly", + __raw_import__: "readonly", + }, + + ecmaVersion: 2025, + sourceType: "module", + }, + + rules: { + "import/export": "error", + "import/exports-last": "error", + "import/extensions": ["error", "always", { ignorePackages: true }], + "import/first": "error", + "import/named": "error", + "import/no-cycle": "error", + "import/no-empty-named-blocks": "error", + "import/no-commonjs": "error", + "import/no-mutable-exports": "error", + "import/no-restricted-paths": [ + "error", + { + zones: [ + { + target: "./web", + from: "./src", + }, + ], + }, + ], + "import/no-self-import": "error", + "import/no-unresolved": [ + "error", + { + ignore: [ + "display", + "pdfjs", + "pdfjs-lib", + "pdfjs-web", + "web", + "fluent-bundle", + "fluent-dom", + // See https://github.com/firebase/firebase-admin-node/discussions/1359. + "eslint-plugin-perfectionist", + ], + }, + ], + "no-unsanitized/method": "error", + "no-unsanitized/property": "error", + "perfectionist/sort-exports": "error", + "perfectionist/sort-named-exports": "error", + "unicorn/no-abusive-eslint-disable": "error", + "unicorn/no-array-reduce": ["error", { allowSimpleOperations: true }], + "unicorn/no-console-spaces": "error", + "unicorn/no-instanceof-builtins": "error", + "unicorn/no-invalid-remove-event-listener": "error", + "unicorn/no-new-buffer": "error", + "unicorn/no-single-promise-in-promise-methods": "error", + "unicorn/no-typeof-undefined": ["error", { checkGlobalVariables: false }], + "unicorn/no-unnecessary-array-flat-depth": "error", + "unicorn/no-unnecessary-array-splice-count": "error", + "unicorn/no-unnecessary-slice-end": "error", + "unicorn/no-useless-promise-resolve-reject": "error", + "unicorn/no-useless-spread": "error", + "unicorn/prefer-array-find": "error", + "unicorn/prefer-array-flat": "error", + "unicorn/prefer-array-flat-map": "error", + "unicorn/prefer-array-index-of": "error", + "unicorn/prefer-array-some": "error", + "unicorn/prefer-at": "error", + "unicorn/prefer-date-now": "error", + "unicorn/prefer-dom-node-append": "error", + "unicorn/prefer-dom-node-remove": "error", + "unicorn/prefer-import-meta-properties": "error", + "unicorn/prefer-includes": "error", + "unicorn/prefer-logical-operator-over-ternary": "error", + "unicorn/prefer-modern-dom-apis": "error", + "unicorn/prefer-modern-math-apis": "error", + "unicorn/prefer-negative-index": "error", + "unicorn/prefer-optional-catch-binding": "error", + "unicorn/prefer-regexp-test": "error", + "unicorn/prefer-single-call": "error", + "unicorn/prefer-string-replace-all": "error", + "unicorn/prefer-string-starts-ends-with": "error", + "unicorn/prefer-ternary": ["error", "only-single-line"], + "unicorn/throw-new-error": "error", + + // Possible errors + "for-direction": "error", + "getter-return": "error", + "no-async-promise-executor": "error", + "no-cond-assign": ["error", "except-parens"], + "no-constant-condition": ["error", { checkLoops: false }], + "no-debugger": "error", + "no-dupe-args": "error", + "no-dupe-else-if": "error", + "no-dupe-keys": "error", + "no-duplicate-case": "error", + "no-empty": ["error", { allowEmptyCatch: true }], + "no-empty-character-class": "error", + "no-ex-assign": "error", + "no-extra-boolean-cast": "error", + "no-func-assign": "error", + "no-inner-declarations": ["error", "functions"], + "no-invalid-regexp": "error", + "no-irregular-whitespace": "error", + "no-loss-of-precision": "error", + "no-obj-calls": "error", + "no-promise-executor-return": "error", + "no-regex-spaces": "error", + "no-setter-return": "error", + "no-sparse-arrays": "error", + "no-template-curly-in-string": "error", + "no-unexpected-multiline": "error", + "no-unreachable": "error", + "no-unsafe-finally": "error", + "no-unsafe-negation": "error", + "no-unsafe-optional-chaining": [ + "error", + { disallowArithmeticOperators: true }, + ], + "no-unused-private-class-members": "error", + "use-isnan": ["error", { enforceForIndexOf: true }], + "valid-typeof": ["error", { requireStringLiterals: true }], + + // Best Practices + "accessor-pairs": [ + "error", + { setWithoutGet: true, enforceForClassMembers: true }, + ], + "consistent-return": "error", + curly: ["error", "all"], + "default-case-last": "error", + "dot-notation": "error", + eqeqeq: ["error", "always"], + "grouped-accessor-pairs": ["error", "getBeforeSet"], + "no-alert": "error", + "no-caller": "error", + "no-else-return": "error", + "no-empty-pattern": "error", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-label": "error", + "no-fallthrough": "error", + "no-floating-decimal": "error", + "no-global-assign": "error", + "no-implied-eval": "error", + "no-iterator": "error", + "no-lone-blocks": "error", + "no-lonely-if": "error", + "no-multi-str": "error", + "no-new": "error", + "no-new-func": "error", + "no-new-symbol": "error", + "no-new-wrappers": "error", + "no-octal-escape": "error", + "no-octal": "error", + "no-redeclare": "error", + "no-return-await": "error", + "no-self-assign": "error", + "no-self-compare": "error", + "no-throw-literal": "error", + "no-unused-expressions": "error", + "no-unused-labels": "error", + "no-useless-call": "error", + "no-useless-catch": "error", + "no-useless-concat": "error", + "no-useless-escape": "error", + "no-useless-return": "error", + "prefer-promise-reject-errors": "error", + "prefer-spread": "error", + "wrap-iife": ["error", "any"], + yoda: ["error", "never", { exceptRange: true }], + + // Strict Mode + strict: ["off", "global"], + + // Variables + "no-delete-var": "error", + "no-label-var": "error", + "no-shadow": "error", + "no-shadow-restricted-names": "error", + "no-undef-init": "error", + "no-undef": ["error", { typeof: true }], + "no-unused-vars": ["error", { vars: "all", args: "none" }], + "no-use-before-define": [ + "error", + { functions: false, classes: false, variables: false }, + ], + + // Stylistic Issues + "lines-between-class-members": ["error", "always"], + "max-len": ["error", { code: 1000, comments: 80, ignoreUrls: true }], + "new-cap": ["error", { newIsCap: true, capIsNew: false }], + "no-array-constructor": "error", + "no-multiple-empty-lines": ["error", { max: 1, maxEOF: 0, maxBOF: 1 }], + "no-nested-ternary": "error", + "no-new-object": "error", + "no-restricted-syntax": [ + "error", + { + selector: + "BinaryExpression[operator='instanceof'][right.name='Object']", + message: "Use `typeof` rather than `instanceof Object`.", + }, + { + selector: "CallExpression[callee.name='assert'][arguments.length!=2]", + message: "`assert()` must always be invoked with two arguments.", + }, + { + selector: "CallExpression[callee.name='isCmd'][arguments.length<2]", + message: + "Use `instanceof Cmd` rather than `isCmd()` with one argument.", + }, + { + selector: "CallExpression[callee.name='isDict'][arguments.length<2]", + message: + "Use `instanceof Dict` rather than `isDict()` with one argument.", + }, + { + selector: "CallExpression[callee.name='isName'][arguments.length<2]", + message: + "Use `instanceof Name` rather than `isName()` with one argument.", + }, + { + selector: "NewExpression[callee.name='Cmd']", + message: "Use `Cmd.get()` rather than `new Cmd()`.", + }, + { + selector: "NewExpression[callee.name='Name']", + message: "Use `Name.get()` rather than `new Name()`.", + }, + { + selector: "NewExpression[callee.name='ObjectLoader']", + message: + "Use `ObjectLoader.load()` rather than `new ObjectLoader()`.", + }, + { + selector: "NewExpression[callee.name='Ref']", + message: "Use `Ref.get()` rather than `new Ref()`.", + }, + { + selector: "ExportNamedDeclaration[declaration]", + message: + "Separate the declaration and the export statement, using `export { ... }`.", + }, + { + selector: "ExportDefaultDeclaration:has(> :declaration)", + message: + "Separate the declaration and the export statement, using `export default `.", + }, + ], + "no-unneeded-ternary": "error", + "operator-assignment": "error", + "prefer-exponentiation-operator": "error", + "spaced-comment": ["error", "always", { block: { balanced: true } }], + + // ECMAScript 6 + "arrow-body-style": ["error", "as-needed"], + "constructor-super": "error", + "no-class-assign": "error", + "no-const-assign": "error", + "no-dupe-class-members": "error", + "no-duplicate-imports": "error", + "no-this-before-super": "error", + "no-useless-computed-key": "error", + "no-useless-constructor": "error", + "no-useless-rename": "error", + "no-var": "error", + "object-shorthand": ["error", "always", { avoidQuotes: true }], + "prefer-const": "error", + "require-yield": "error", + "sort-imports": ["error", { ignoreCase: true }], + "template-curly-spacing": ["error", "never"], + }, + }, + { + files: jsFiles("src"), + rules: { + "no-console": "error", + }, + }, + + /* ======================================================================== *\ + Test-specific rules + \* ======================================================================== */ + + { + files: jsFiles("test"), + + plugins: { jasmine }, + + languageOptions: { + globals: { + ...globals.node, + ...globals.jasmine, + }, + }, + rules: { + ...jasmine.configs.recommended.rules, + "jasmine/new-line-before-expect": "off", + "jasmine/new-line-between-declarations": "off", + "jasmine/no-focused-tests": "error", + "jasmine/no-pending-tests": "off", + "jasmine/no-spec-dupes": ["error", "branch"], + "jasmine/no-suite-dupes": ["error", "branch"], + "jasmine/prefer-jasmine-matcher": "off", + "jasmine/prefer-toHaveBeenCalledWith": "off", + }, + }, + { + files: jsFiles("test/unit"), + rules: { + "import/no-unresolved": ["error", { ignore: ["pdfjs/"] }], + "no-console": ["error", { allow: ["warn", "error"] }], + }, + }, + { + files: jsFiles("test/integration"), + rules: { + "no-console": ["error", { allow: ["warn", "error"] }], + "no-restricted-syntax": [ + "error", + { + selector: "CallExpression[callee.name='waitForTimeout']", + message: + "`waitForTimeout` can cause intermittent failures and should not be used (see issue #17656 for replacements).", + }, + ], + }, + }, + + /* ======================================================================== *\ + External libraries + \* ======================================================================== */ + + { + files: jsFiles("external"), + + languageOptions: { globals: globals.node }, + }, + + /* ======================================================================== *\ + Examples + \* ======================================================================== */ + + { + files: jsFiles("examples"), + + languageOptions: { + globals: { + pdfjsImageDecoders: false, + pdfjsLib: false, + pdfjsViewer: false, + }, + }, + }, + { + files: [...jsFiles("examples/node"), ...jsFiles("examples/webpack")], + + languageOptions: { globals: globals.node }, + }, + + /* ======================================================================== *\ + Chromium extension + \* ======================================================================== */ + + { + files: jsFiles("extensions/chromium"), + + languageOptions: { + globals: globals.webextensions, + sourceType: "script", + }, + + rules: { + "no-var": "off", + }, + }, + { + files: chromiumExtensionServiceWorkerFiles, + + languageOptions: { + globals: globals.serviceworker, + sourceType: "script", + }, + }, + + /* ======================================================================== *\ + Other + \* ======================================================================== */ + { + files: ["gulpfile.mjs"], + languageOptions: { globals: globals.node }, + }, +]; diff --git a/examples/.eslintrc b/examples/.eslintrc deleted file mode 100644 index 433abdbfab2c4..0000000000000 --- a/examples/.eslintrc +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": [ - "../.eslintrc" - ], - - "globals": { - "pdfjsImageDecoders": false, - "pdfjsLib": false, - "pdfjsViewer": false, - }, -} diff --git a/examples/learning/prevnext.html b/examples/learning/prevnext.html index 40c1488c8b99d..e1043bf1e8fad 100644 --- a/examples/learning/prevnext.html +++ b/examples/learning/prevnext.html @@ -9,8 +9,8 @@

'Previous/Next' example

- - + +     Page: /
diff --git a/examples/mobile-viewer/viewer.css b/examples/mobile-viewer/viewer.css index b58dc91419481..bd27157a46132 100644 --- a/examples/mobile-viewer/viewer.css +++ b/examples/mobile-viewer/viewer.css @@ -113,8 +113,8 @@ footer { background-color: rgb(0 0 0 / 0); font-size: 1.2rem; color: rgb(255 255 255 / 1); - background-image: url(images/div_line_left.png), - url(images/div_line_right.png); + background-image: + url(images/div_line_left.png), url(images/div_line_right.png); background-repeat: no-repeat; background-position: left, right; background-size: 0.2rem, 0.2rem; diff --git a/examples/mobile-viewer/viewer.html b/examples/mobile-viewer/viewer.html index 50bbfd7811411..6bd8b540647be 100644 --- a/examples/mobile-viewer/viewer.html +++ b/examples/mobile-viewer/viewer.html @@ -43,13 +43,13 @@

- - + + - - + +
diff --git a/examples/mobile-viewer/viewer.mjs b/examples/mobile-viewer/viewer.mjs index fedd5d8e23ead..bf496c91e62aa 100644 --- a/examples/mobile-viewer/viewer.mjs +++ b/examples/mobile-viewer/viewer.mjs @@ -91,10 +91,10 @@ const PDFViewerApplication = { let key = "pdfjs-loading-error"; if (reason instanceof pdfjsLib.InvalidPDFException) { key = "pdfjs-invalid-file-error"; - } else if (reason instanceof pdfjsLib.MissingPDFException) { - key = "pdfjs-missing-file-error"; - } else if (reason instanceof pdfjsLib.UnexpectedResponseException) { - key = "pdfjs-unexpected-response-error"; + } else if (reason instanceof pdfjsLib.ResponseException) { + key = reason.missing + ? "pdfjs-missing-file-error" + : "pdfjs-unexpected-response-error"; } self.l10n.get(key).then(msg => { self.error(msg, { message: reason?.message }); diff --git a/examples/node/.eslintrc b/examples/node/.eslintrc deleted file mode 100644 index 46f120c21a4f5..0000000000000 --- a/examples/node/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": [ - "../.eslintrc" - ], - - "env": { - "node": true, - }, -} diff --git a/examples/node/pdf2png/README.md b/examples/node/pdf2png/README.md index 00b388b9f606e..8b114061b2361 100644 --- a/examples/node/pdf2png/README.md +++ b/examples/node/pdf2png/README.md @@ -9,9 +9,7 @@ Install the dependencies and build the PDF.js library: $ npm install $ gulp dist-install -Install the Node canvas library and run the example to convert the first page of a -PDF file to a PNG image: +Run the example to convert the first page of a PDF file to a PNG image: - $ npm install canvas $ cd examples/node/pdf2png - $ node pdf2png.js + $ node pdf2png.mjs diff --git a/examples/node/pdf2png/pdf2png.mjs b/examples/node/pdf2png/pdf2png.mjs index 3dfd9b089aa40..ed01834b07571 100644 --- a/examples/node/pdf2png/pdf2png.mjs +++ b/examples/node/pdf2png/pdf2png.mjs @@ -13,41 +13,9 @@ * limitations under the License. */ -import { strict as assert } from "assert"; -import Canvas from "canvas"; import fs from "fs"; import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs"; -class NodeCanvasFactory { - create(width, height) { - assert(width > 0 && height > 0, "Invalid canvas size"); - const canvas = Canvas.createCanvas(width, height); - const context = canvas.getContext("2d"); - return { - canvas, - context, - }; - } - - reset(canvasAndContext, width, height) { - assert(canvasAndContext.canvas, "Canvas is not specified"); - assert(width > 0 && height > 0, "Invalid canvas size"); - canvasAndContext.canvas.width = width; - canvasAndContext.canvas.height = height; - } - - destroy(canvasAndContext) { - assert(canvasAndContext.canvas, "Canvas is not specified"); - - // Zeroing the width and height cause Firefox to release graphics - // resources immediately, which can greatly reduce memory consumption. - canvasAndContext.canvas.width = 0; - canvasAndContext.canvas.height = 0; - canvasAndContext.canvas = null; - canvasAndContext.context = null; - } -} - // Some PDFs need external cmaps. const CMAP_URL = "../../../node_modules/pdfjs-dist/cmaps/"; const CMAP_PACKED = true; @@ -56,8 +24,6 @@ const CMAP_PACKED = true; const STANDARD_FONT_DATA_URL = "../../../node_modules/pdfjs-dist/standard_fonts/"; -const canvasFactory = new NodeCanvasFactory(); - // Loading file from file system into typed array. const pdfPath = process.argv[2] || "../../../web/compressed.tracemonkey-pldi-09.pdf"; @@ -69,7 +35,6 @@ const loadingTask = getDocument({ cMapUrl: CMAP_URL, cMapPacked: CMAP_PACKED, standardFontDataUrl: STANDARD_FONT_DATA_URL, - canvasFactory, }); try { @@ -78,6 +43,7 @@ try { // Get the first page. const page = await pdfDocument.getPage(1); // Render the page on a Node canvas with 100% scale. + const canvasFactory = pdfDocument.canvasFactory; const viewport = page.getViewport({ scale: 1.0 }); const canvasAndContext = canvasFactory.create( viewport.width, @@ -91,7 +57,7 @@ try { const renderTask = page.render(renderContext); await renderTask.promise; // Convert the canvas to an image buffer. - const image = canvasAndContext.canvas.toBuffer(); + const image = canvasAndContext.canvas.toBuffer("image/png"); fs.writeFile("output.png", image, function (error) { if (error) { console.error("Error: " + error); diff --git a/examples/webpack/.eslintrc b/examples/webpack/.eslintrc deleted file mode 100644 index 46f120c21a4f5..0000000000000 --- a/examples/webpack/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": [ - "../.eslintrc" - ], - - "env": { - "node": true, - }, -} diff --git a/extensions/chromium/.eslintrc b/extensions/chromium/.eslintrc deleted file mode 100644 index 961e868308f74..0000000000000 --- a/extensions/chromium/.eslintrc +++ /dev/null @@ -1,22 +0,0 @@ -{ - "extends": [ - ../../.eslintrc - ], - - "env": { - "webextensions": true - }, - - "plugins": [ - "mozilla" - ], - - "parserOptions": { - "sourceType": "script" - }, - - "rules": { - "mozilla/import-globals": "error", - "no-var": "off", - }, -} diff --git a/extensions/chromium/restoretab.html b/extensions/chromium/background.js similarity index 71% rename from extensions/chromium/restoretab.html rename to extensions/chromium/background.js index 6e6fa425dd559..bb448be549c0c 100644 --- a/extensions/chromium/restoretab.html +++ b/extensions/chromium/background.js @@ -1,6 +1,5 @@ - - - +*/ + +"use strict"; + +importScripts( + "options/migration.js", + "preserve-referer.js", + "pdfHandler.js", + "extension-router.js", + "suppress-update.js", + "telemetry.js" +); diff --git a/extensions/chromium/contentscript.js b/extensions/chromium/contentscript.js index aa6edb7b3f3aa..83ebf96a225c7 100644 --- a/extensions/chromium/contentscript.js +++ b/extensions/chromium/contentscript.js @@ -16,13 +16,16 @@ limitations under the License. "use strict"; -var VIEWER_URL = chrome.extension.getURL("content/web/viewer.html"); +var VIEWER_URL = chrome.runtime.getURL("content/web/viewer.html"); function getViewerURL(pdf_url) { return VIEWER_URL + "?file=" + encodeURIComponent(pdf_url); } document.addEventListener("animationstart", onAnimationStart, true); +if (document.contentType === "application/pdf") { + chrome.runtime.sendMessage({ action: "canRequestBody" }, maybeRenderPdfDoc); +} function onAnimationStart(event) { if (event.animationName === "pdfjs-detected-object-or-embed") { @@ -221,3 +224,38 @@ function getEmbeddedViewerURL(path) { path = a.href; return getViewerURL(path) + fragment; } + +function maybeRenderPdfDoc(isNotPOST) { + if (!isNotPOST) { + // The document was loaded through a POST request, but we cannot access the + // original response body, nor safely send a new request to fetch the PDF. + // Until #4483 is fixed, POST requests should be ignored. + return; + } + + // Detected PDF that was not redirected by the declarativeNetRequest rules. + // Maybe because this was served without Content-Type and sniffed as PDF. + // Or because this is Chrome 127-, which does not support responseHeaders + // condition in declarativeNetRequest (DNR), and PDF requests are therefore + // not redirected via DNR. + + // In any case, load the viewer. + console.log(`Detected PDF via document, opening viewer for ${document.URL}`); + + // Ideally we would use logic consistent with the DNR logic, like this: + // location.href = getEmbeddedViewerURL(document.URL); + // ... unfortunately, this causes Chrome to crash until version 129, fixed by + // https://chromium.googlesource.com/chromium/src/+/8c42358b2cc549553d939efe7d36515d80563da7%5E%21/ + // Work around this by replacing the body with an iframe of the viewer. + // Interestingly, Chrome's built-in PDF viewer uses a similar technique. + const shadowRoot = document.body.attachShadow({ mode: "closed" }); + const iframe = document.createElement("iframe"); + iframe.style.position = "absolute"; + iframe.style.top = "0"; + iframe.style.left = "0"; + iframe.style.width = "100%"; + iframe.style.height = "100%"; + iframe.style.border = "0 none"; + iframe.src = getEmbeddedViewerURL(document.URL); + shadowRoot.append(iframe); +} diff --git a/extensions/chromium/extension-router.js b/extensions/chromium/extension-router.js index ecb9004d822bc..f9720e12b2df0 100644 --- a/extensions/chromium/extension-router.js +++ b/extensions/chromium/extension-router.js @@ -17,13 +17,12 @@ limitations under the License. "use strict"; (function ExtensionRouterClosure() { - var VIEWER_URL = chrome.extension.getURL("content/web/viewer.html"); - var CRX_BASE_URL = chrome.extension.getURL("/"); + var VIEWER_URL = chrome.runtime.getURL("content/web/viewer.html"); + var CRX_BASE_URL = chrome.runtime.getURL("/"); var schemes = [ "http", "https", - "ftp", "file", "chrome-extension", "blob", @@ -47,6 +46,7 @@ limitations under the License. } var scheme = url.slice(0, schemeIndex).toLowerCase(); if (schemes.includes(scheme)) { + // NOTE: We cannot use the `updateUrlHash` function in this context. url = url.split("#", 1)[0]; if (url.charAt(schemeIndex) === ":") { url = encodeURIComponent(url); @@ -56,73 +56,50 @@ limitations under the License. return undefined; } - // TODO(rob): Use declarativeWebRequest once declared URL-encoding is - // supported, see http://crbug.com/273589 - // (or rewrite the query string parser in viewer.js to get it to - // recognize the non-URL-encoded PDF URL.) - chrome.webRequest.onBeforeRequest.addListener( - function (details) { + function resolveViewerURL(originalUrl) { + if (originalUrl.startsWith(CRX_BASE_URL)) { // This listener converts chrome-extension://.../http://...pdf to // chrome-extension://.../content/web/viewer.html?file=http%3A%2F%2F...pdf - var url = parseExtensionURL(details.url); + var url = parseExtensionURL(originalUrl); if (url) { url = VIEWER_URL + "?file=" + url; - var i = details.url.indexOf("#"); + var i = originalUrl.indexOf("#"); if (i > 0) { - url += details.url.slice(i); + url += originalUrl.slice(i); } - console.log("Redirecting " + details.url + " to " + url); - return { redirectUrl: url }; - } - return undefined; - }, - { - types: ["main_frame", "sub_frame"], - urls: schemes.map(function (scheme) { - // Format: "chrome-extension://[EXTENSIONID]/*" - return CRX_BASE_URL + scheme + "*"; - }), - }, - ["blocking"] - ); - - // When session restore is used, viewer pages may be loaded before the - // webRequest event listener is attached (= page not found). - // Or the extension could have been crashed (OOM), leaving a sad tab behind. - // Reload these tabs. - chrome.tabs.query( - { - url: CRX_BASE_URL + "*:*", - }, - function (tabsFromLastSession) { - for (const { id } of tabsFromLastSession) { - chrome.tabs.reload(id); + return url; } } - ); - console.log("Set up extension URL router."); + return undefined; + } - Object.keys(localStorage).forEach(function (key) { - // The localStorage item is set upon unload by chromecom.js. - var parsedKey = /^unload-(\d+)-(true|false)-(.+)/.exec(key); - if (parsedKey) { - var timeStart = parseInt(parsedKey[1], 10); - var isHidden = parsedKey[2] === "true"; - var url = parsedKey[3]; - if (Date.now() - timeStart < 3000) { - // Is it a new item (younger than 3 seconds)? Assume that the extension - // just reloaded, so restore the tab (work-around for crbug.com/511670). - chrome.tabs.create({ - url: - chrome.runtime.getURL("restoretab.html") + - "?" + - encodeURIComponent(url) + - "#" + - encodeURIComponent(localStorage.getItem(key)), - active: !isHidden, - }); + self.addEventListener("fetch", event => { + const req = event.request; + if (req.destination === "document") { + var url = resolveViewerURL(req.url); + if (url) { + console.log("Redirecting " + req.url + " to " + url); + event.respondWith(Response.redirect(url)); } - localStorage.removeItem(key); } }); + + // Ctrl + F5 bypasses service worker. the pretty extension URLs will fail to + // resolve in that case. Catch this and redirect to destination. + chrome.webNavigation.onErrorOccurred.addListener( + details => { + if (details.frameId !== 0) { + // Not a top-level frame. Cannot easily navigate a specific child frame. + return; + } + const url = resolveViewerURL(details.url); + if (url) { + console.log(`Redirecting ${details.url} to ${url} (fallback)`); + chrome.tabs.update(details.tabId, { url }); + } + }, + { url: [{ urlPrefix: CRX_BASE_URL }] } + ); + + console.log("Set up extension URL router."); })(); diff --git a/extensions/chromium/icon19.png b/extensions/chromium/icon19.png deleted file mode 100644 index 1f67a12883e1f..0000000000000 Binary files a/extensions/chromium/icon19.png and /dev/null differ diff --git a/extensions/chromium/icon38.png b/extensions/chromium/icon38.png deleted file mode 100644 index 227452fb7d04c..0000000000000 Binary files a/extensions/chromium/icon38.png and /dev/null differ diff --git a/extensions/chromium/manifest.json b/extensions/chromium/manifest.json index e90ae5700e72e..60c99d9ac4da8 100644 --- a/extensions/chromium/manifest.json +++ b/extensions/chromium/manifest.json @@ -1,6 +1,6 @@ { - "minimum_chrome_version": "88", - "manifest_version": 2, + "minimum_chrome_version": "103", + "manifest_version": 3, "name": "PDF Viewer", "version": "PDFJSSCRIPT_VERSION", "description": "Uses HTML5 to display PDF files directly in the browser.", @@ -10,61 +10,52 @@ "16": "icon16.png" }, "permissions": [ - "fileBrowserHandler", + "alarms", + "declarativeNetRequestWithHostAccess", "webRequest", - "webRequestBlocking", - "", "tabs", "webNavigation", "storage" ], + "host_permissions": [""], "content_scripts": [ { - "matches": ["http://*/*", "https://*/*", "ftp://*/*", "file://*/*"], + "matches": ["http://*/*", "https://*/*", "file://*/*"], "run_at": "document_start", "all_frames": true, "css": ["contentstyle.css"], "js": ["contentscript.js"] } ], - "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", - "file_browser_handlers": [ - { - "id": "open-as-pdf", - "default_title": "Open with PDF Viewer", - "file_filters": ["filesystem:*.pdf"] - } - ], + "content_security_policy": { + "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'" + }, "storage": { "managed_schema": "preferences_schema.json" }, "options_ui": { - "page": "options/options.html", - "chrome_style": true + "page": "options/options.html" }, "options_page": "options/options.html", "background": { - "page": "pdfHandler.html" - }, - "page_action": { - "default_icon": { - "19": "icon19.png", - "38": "icon38.png" - }, - "default_title": "Show PDF URL", - "default_popup": "pageActionPopup.html" + "service_worker": "background.js" }, "incognito": "split", "web_accessible_resources": [ - "content/web/viewer.html", - "http:/*", - "https:/*", - "ftp:/*", - "file:/*", - "chrome-extension:/*", - "blob:*", - "data:*", - "filesystem:/*", - "drive:*" + { + "resources": [ + "content/web/viewer.html", + "http:/*", + "https:/*", + "file:/*", + "chrome-extension:/*", + "blob:*", + "data:*", + "filesystem:/*", + "drive:*" + ], + "matches": [""], + "extension_ids": ["*"] + } ] } diff --git a/extensions/chromium/options/migration.js b/extensions/chromium/options/migration.js index dd8fb6ef73d1a..9b084e45ae3f9 100644 --- a/extensions/chromium/options/migration.js +++ b/extensions/chromium/options/migration.js @@ -13,10 +13,14 @@ 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. */ -/* eslint strict: ["error", "function"] */ +"use strict"; -(function () { - "use strict"; +chrome.runtime.onInstalled.addListener(({ reason }) => { + if (reason !== "update") { + // We only need to run migration logic for extension updates, not for new + // installs or browser updates. + return; + } var storageLocal = chrome.storage.local; var storageSync = chrome.storage.sync; @@ -37,16 +41,12 @@ limitations under the License. }); }); - function getStorageNames(callback) { - var x = new XMLHttpRequest(); + async function getStorageNames(callback) { var schema_location = chrome.runtime.getManifest().storage.managed_schema; - x.open("get", chrome.runtime.getURL(schema_location)); - x.onload = function () { - var storageKeys = Object.keys(x.response.properties); - callback(storageKeys); - }; - x.responseType = "json"; - x.send(); + var res = await fetch(chrome.runtime.getURL(schema_location)); + var storageManifest = await res.json(); + var storageKeys = Object.keys(storageManifest.properties); + callback(storageKeys); } // Save |values| to storage.sync and delete the values with that key from @@ -150,4 +150,4 @@ limitations under the License. } ); } -})(); +}); diff --git a/extensions/chromium/options/options.html b/extensions/chromium/options/options.html index 8d5661fcbdc3b..bd18c2456465f 100644 --- a/extensions/chromium/options/options.html +++ b/extensions/chromium/options/options.html @@ -19,23 +19,28 @@ PDF.js viewer options
- +