Compare commits

..

No commits in common. "master" and "v4.4.168" have entirely different histories.

693 changed files with 33021 additions and 88389 deletions

13
.eslintignore Normal file
View File

@ -0,0 +1,13 @@
build/
l10n/
docs/
node_modules/
external/bcmaps/
external/builder/fixtures/
external/builder/fixtures_babel/
external/quickjs/
external/openjpeg/
test/tmp/
test/pdfs/
web/locale/
*~/

267
.eslintrc Normal file
View File

@ -0,0 +1,267 @@
{
"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"],
},
}

View File

@ -5,8 +5,6 @@ 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
@ -16,7 +14,6 @@ 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
@ -27,13 +24,11 @@ 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:

View File

@ -1,4 +1,4 @@
blank_issues_enabled: false
blank_issues_enabled: true
contact_links:
- name: Need help?
url: https://github.com/mozilla/pdf.js/discussions

View File

@ -25,5 +25,3 @@ CO01:
exclusions:
files: []
messages: []
VC:
disabled: false

View File

@ -11,16 +11,16 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [20, 22, 24]
node-version: [18, lts/*, 21]
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

View File

@ -18,18 +18,18 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
queries: security-and-quality
- name: Autobuild CodeQL
uses: github/codeql-action/autobuild@v4
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v4
uses: github/codeql-action/analyze@v3

View File

@ -25,12 +25,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Use Python 3.14
uses: actions/setup-python@v6
- name: Use Python 3.12
uses: actions/setup-python@v5
with:
python-version: '3.14'
python-version: '3.12'
cache: 'pip'
- name: Install Fluent dependencies

View File

@ -36,22 +36,22 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm ci
- name: Use Python 3.14
uses: actions/setup-python@v6
- name: Use Python 3.12
uses: actions/setup-python@v5
with:
python-version: '3.14'
python-version: '3.12'
cache: 'pip'
- name: Install Fonttools

View File

@ -15,12 +15,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

View File

@ -17,12 +17,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
registry-url: 'https://registry.npmjs.org'
@ -34,4 +34,6 @@ jobs:
run: npx gulp dist
- name: Publish the `pdfjs-dist` library to NPM
run: npm publish ./build/dist
run: npm publish ./build/dist --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@ -17,12 +17,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
@ -49,7 +49,7 @@ jobs:
INPUT_PATH: build/gh-pages
- name: Upload the website
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v4
with:
name: github-pages
path: ${{ runner.temp }}/website.tar

View File

@ -15,12 +15,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

7
.gitpod.Dockerfile vendored Normal file
View File

@ -0,0 +1,7 @@
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/*

13
.gitpod.yml Normal file
View File

@ -0,0 +1,13 @@
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

View File

@ -5,10 +5,7 @@ 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/

View File

@ -9,17 +9,10 @@
"overrides": [
{
"files": ["tsconfig.json", ".prettierrc"],
"options": {
"parser": "json"
}
files: ["tsconfig.json"],
options: {
parser: "json",
},
},
{
"files": ["**/*.html"],
"options": {
"parser": "html",
"printWidth": 160
}
}
]
}

View File

@ -1,9 +0,0 @@
{
"chrome": {
"skipDownload": false
},
"firefox": {
"skipDownload": false,
"version": "nightly"
}
}

View File

@ -6,7 +6,6 @@ external/bcmaps/
external/builder/fixtures/
external/builder/fixtures_babel/
external/quickjs/
test/stats/results/
test/tmp/
test/pdfs/
web/locale/

View File

@ -1,35 +0,0 @@
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/**",
"*~/**",
],
};

View File

@ -8,21 +8,16 @@ Andreas Gal <gal@mozilla.com>
Artur Adib <aadib@mozilla.com>
Brendan Dahl <bdahl@mozilla.com>
Bill Walker <bwalker@mozilla.com>
Calixte Denizet <calixte@mozilla.com>
Chris G Jones <cjones@mozilla.com>
David Quintana <gigaherz@gmail.com>
Emily Wachowiak <ewachowiak@mozilla.com>
Felix Kälberer <@fkaelberer>
Jakob Miland <saebekassebil@gmail.com>
Jonas Jenwald <jonas.jenwald@gmail.com>
Julian Viereck
Justin D'Arcangelo <justindarc@gmail.com>
Kalervo Kujala
Marco Castelluccio <mcastelluccio@mozilla.com>
Marie-Lilas Onanga Ozavino <monangaozavino@mozilla.com>
Michał Gołębiowski-Owczarek <m.goleb@gmail.com>
Ophir Lojkine <@lovasoa>
Ryan Casey <rcasey@mozilla.com>
Rob Wu <rob@robwu.nl>
Shaon Barman <shaon.barman@gmail.com>
Sehyun Park <premed055515@gmail.com>

View File

@ -1,4 +1,4 @@
# 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 [![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](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?q=is%3Aissue%20state%3Aopen%20label%3Agood-beginner-bug)
+ [Good Beginner Bugs](https://github.com/mozilla/pdf.js/issues?direction=desc&labels=good-beginner-bug&page=1&sort=created&state=open)
+ [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,6 +57,9 @@ 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:
@ -90,7 +93,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 under
or to build it from source. We supply pre-built versions for usage with NPM and Bower 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.
@ -134,4 +137,4 @@ Talk to us on Matrix:
File an issue:
+ https://github.com/mozilla/pdf.js/issues/new/choose
+ https://github.com/mozilla/pdf.js/issues/new

View File

@ -1,473 +0,0 @@
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 <variable name>`.",
},
],
"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 },
},
];

11
examples/.eslintrc Normal file
View File

@ -0,0 +1,11 @@
{
"extends": [
"../.eslintrc"
],
"globals": {
"pdfjsImageDecoders": false,
"pdfjsLib": false,
"pdfjsViewer": false,
},
}

View File

@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<!--
Copyright 2014 Mozilla Foundation
@ -15,29 +15,29 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<html dir="ltr" mozdisallowselectionprint>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<meta name="google" content="notranslate" />
<title>PDF.js page viewer using built components</title>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="google" content="notranslate">
<title>PDF.js page viewer using built components</title>
<style>
body {
background-color: #808080;
margin: 0;
padding: 0;
}
</style>
<style>
body {
background-color: #808080;
margin: 0;
padding: 0;
}
</style>
<link rel="stylesheet" href="../../node_modules/pdfjs-dist/web/pdf_viewer.css" />
<link rel="stylesheet" href="../../node_modules/pdfjs-dist/web/pdf_viewer.css">
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
<script src="../../node_modules/pdfjs-dist/web/pdf_viewer.mjs" type="module"></script>
</head>
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
<script src="../../node_modules/pdfjs-dist/web/pdf_viewer.mjs" type="module"></script>
</head>
<body tabindex="1">
<div id="pageContainer" class="pdfViewer singlePageView"></div>
<body tabindex="1">
<div id="pageContainer" class="pdfViewer singlePageView"></div>
<script src="pageviewer.mjs" type="module"></script>
</body>
<script src="pageviewer.mjs" type="module"></script>
</body>
</html>

View File

@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<!--
Copyright 2014 Mozilla Foundation
@ -15,37 +15,37 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<html dir="ltr" mozdisallowselectionprint>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<meta name="google" content="notranslate" />
<title>PDF.js viewer using built components</title>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="google" content="notranslate">
<title>PDF.js viewer using built components</title>
<style>
body {
background-color: #808080;
margin: 0;
padding: 0;
}
#viewerContainer {
overflow: auto;
position: absolute;
width: 100%;
height: 100%;
}
</style>
<style>
body {
background-color: #808080;
margin: 0;
padding: 0;
}
#viewerContainer {
overflow: auto;
position: absolute;
width: 100%;
height: 100%;
}
</style>
<link rel="stylesheet" href="../../node_modules/pdfjs-dist/web/pdf_viewer.css" />
<link rel="stylesheet" href="../../node_modules/pdfjs-dist/web/pdf_viewer.css">
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
<script src="../../node_modules/pdfjs-dist/web/pdf_viewer.mjs" type="module"></script>
</head>
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
<script src="../../node_modules/pdfjs-dist/web/pdf_viewer.mjs" type="module"></script>
</head>
<body tabindex="1">
<div id="viewerContainer">
<div id="viewer" class="pdfViewer"></div>
</div>
<body tabindex="1">
<div id="viewerContainer">
<div id="viewer" class="pdfViewer"></div>
</div>
<script src="simpleviewer.mjs" type="module"></script>
</body>
<script src="simpleviewer.mjs" type="module"></script>
</body>
</html>

View File

@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<!--
Copyright 2014 Mozilla Foundation
@ -15,37 +15,37 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<html dir="ltr" mozdisallowselectionprint>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<meta name="google" content="notranslate" />
<title>PDF.js Single Page Viewer using built components</title>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="google" content="notranslate">
<title>PDF.js Single Page Viewer using built components</title>
<style>
body {
background-color: #808080;
margin: 0;
padding: 0;
}
#viewerContainer {
overflow: auto;
position: absolute;
width: 100%;
height: 100%;
}
</style>
<style>
body {
background-color: #808080;
margin: 0;
padding: 0;
}
#viewerContainer {
overflow: auto;
position: absolute;
width: 100%;
height: 100%;
}
</style>
<link rel="stylesheet" href="../../node_modules/pdfjs-dist/web/pdf_viewer.css" />
<link rel="stylesheet" href="../../node_modules/pdfjs-dist/web/pdf_viewer.css">
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
<script src="../../node_modules/pdfjs-dist/web/pdf_viewer.mjs" type="module"></script>
</head>
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
<script src="../../node_modules/pdfjs-dist/web/pdf_viewer.mjs" type="module"></script>
</head>
<body tabindex="1">
<div id="viewerContainer">
<div id="viewer" class="pdfViewer"></div>
</div>
<body tabindex="1">
<div id="viewerContainer">
<div id="viewer" class="pdfViewer"></div>
</div>
<script src="singlepageviewer.mjs" type="module"></script>
</body>
<script src="singlepageviewer.mjs" type="module"></script>
</body>
</html>

View File

@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<!--
Copyright 2018 Mozilla Foundation
@ -15,26 +15,26 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<html dir="ltr" mozdisallowselectionprint>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<meta name="google" content="notranslate" />
<title>PDF.js standalone JpegImage parser</title>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="google" content="notranslate">
<title>PDF.js standalone JpegImage parser</title>
<style>
body {
background-color: #808080;
margin: 0;
padding: 0;
}
</style>
<style>
body {
background-color: #808080;
margin: 0;
padding: 0;
}
</style>
<script src="../../node_modules/pdfjs-dist/image_decoders/pdf.image_decoders.mjs" type="module"></script>
</head>
<script src="../../node_modules/pdfjs-dist/image_decoders/pdf.image_decoders.mjs" type="module"></script>
</head>
<body tabindex="1">
<canvas id="jpegCanvas" width="0" height="0"></canvas>
<body tabindex="1">
<canvas id="jpegCanvas" width="0" height="0"></canvas>
<script src="jpeg_viewer.mjs" type="module"></script>
</body>
<script src="jpeg_viewer.mjs" type="module"></script>
</body>
</html>

View File

@ -1,71 +1,76 @@
<!doctype html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>'Hello, world!' example</title>
</head>
<body>
<h1>'Hello, world!' example</h1>
<head>
<meta charset="UTF-8">
<title>'Hello, world!' example</title>
</head>
<body>
<canvas id="the-canvas" style="border: 1px solid black; direction: ltr"></canvas>
<h1>'Hello, world!' example</h1>
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
<canvas id="the-canvas" style="border: 1px solid black; direction: ltr;"></canvas>
<script id="script" type="module">
//
// If absolute URL from the remote server is provided, configure the CORS
// header on that server.
//
const url = "./helloworld.pdf";
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
//
// The workerSrc property shall be specified.
//
pdfjsLib.GlobalWorkerOptions.workerSrc = "../../node_modules/pdfjs-dist/build/pdf.worker.mjs";
<script id="script" type="module">
//
// If absolute URL from the remote server is provided, configure the CORS
// header on that server.
//
const url = './helloworld.pdf';
//
// Asynchronous download PDF
//
const loadingTask = pdfjsLib.getDocument(url);
const pdf = await loadingTask.promise;
//
// Fetch the first page
//
const page = await pdf.getPage(1);
const scale = 1.5;
const viewport = page.getViewport({ scale });
// Support HiDPI-screens.
const outputScale = window.devicePixelRatio || 1;
//
// The workerSrc property shall be specified.
//
pdfjsLib.GlobalWorkerOptions.workerSrc =
'../../node_modules/pdfjs-dist/build/pdf.worker.mjs';
//
// Prepare canvas using PDF page dimensions
//
const canvas = document.getElementById("the-canvas");
const context = canvas.getContext("2d");
//
// Asynchronous download PDF
//
const loadingTask = pdfjsLib.getDocument(url);
const pdf = await loadingTask.promise;
//
// Fetch the first page
//
const page = await pdf.getPage(1);
const scale = 1.5;
const viewport = page.getViewport({ scale });
// Support HiDPI-screens.
const outputScale = window.devicePixelRatio || 1;
canvas.width = Math.floor(viewport.width * outputScale);
canvas.height = Math.floor(viewport.height * outputScale);
canvas.style.width = Math.floor(viewport.width) + "px";
canvas.style.height = Math.floor(viewport.height) + "px";
//
// Prepare canvas using PDF page dimensions
//
const canvas = document.getElementById("the-canvas");
const context = canvas.getContext("2d");
const transform = outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null;
canvas.width = Math.floor(viewport.width * outputScale);
canvas.height = Math.floor(viewport.height * outputScale);
canvas.style.width = Math.floor(viewport.width) + "px";
canvas.style.height = Math.floor(viewport.height) + "px";
//
// Render PDF page into canvas context
//
const renderContext = {
canvasContext: context,
transform,
viewport,
};
page.render(renderContext);
</script>
const transform = outputScale !== 1
? [outputScale, 0, 0, outputScale, 0, 0]
: null;
<hr />
<h2>JavaScript code:</h2>
<pre id="code"></pre>
<script>
document.getElementById("code").textContent = document.getElementById("script").text;
</script>
</body>
//
// Render PDF page into canvas context
//
const renderContext = {
canvasContext: context,
transform,
viewport,
};
page.render(renderContext);
</script>
<hr>
<h2>JavaScript code:</h2>
<pre id="code"></pre>
<script>
document.getElementById('code').textContent =
document.getElementById('script').text;
</script>
</body>
</html>

View File

@ -1,77 +1,81 @@
<!doctype html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>'Hello, world!' base64 example</title>
</head>
<body>
<h1>'Hello, world!' example</h1>
<head>
<meta charset="UTF-8">
<title>'Hello, world!' base64 example</title>
</head>
<body>
<canvas id="the-canvas" style="border: 1px solid black; direction: ltr"></canvas>
<h1>'Hello, world!' example</h1>
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
<canvas id="the-canvas" style="border: 1px solid black; direction: ltr;"></canvas>
<script id="script" type="module">
// atob() is used to convert base64 encoded PDF to binary-like data.
// (See also https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/
// Base64_encoding_and_decoding.)
var pdfData = atob(
"JVBERi0xLjcKCjEgMCBvYmogICUgZW50cnkgcG9pbnQKPDwKICAvVHlwZSAvQ2F0YWxvZwog" +
"IC9QYWdlcyAyIDAgUgo+PgplbmRvYmoKCjIgMCBvYmoKPDwKICAvVHlwZSAvUGFnZXMKICAv" +
"TWVkaWFCb3ggWyAwIDAgMjAwIDIwMCBdCiAgL0NvdW50IDEKICAvS2lkcyBbIDMgMCBSIF0K" +
"Pj4KZW5kb2JqCgozIDAgb2JqCjw8CiAgL1R5cGUgL1BhZ2UKICAvUGFyZW50IDIgMCBSCiAg" +
"L1Jlc291cmNlcyA8PAogICAgL0ZvbnQgPDwKICAgICAgL0YxIDQgMCBSIAogICAgPj4KICA+" +
"PgogIC9Db250ZW50cyA1IDAgUgo+PgplbmRvYmoKCjQgMCBvYmoKPDwKICAvVHlwZSAvRm9u" +
"dAogIC9TdWJ0eXBlIC9UeXBlMQogIC9CYXNlRm9udCAvVGltZXMtUm9tYW4KPj4KZW5kb2Jq" +
"Cgo1IDAgb2JqICAlIHBhZ2UgY29udGVudAo8PAogIC9MZW5ndGggNDQKPj4Kc3RyZWFtCkJU" +
"CjcwIDUwIFRECi9GMSAxMiBUZgooSGVsbG8sIHdvcmxkISkgVGoKRVQKZW5kc3RyZWFtCmVu" +
"ZG9iagoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDEwIDAwMDAwIG4g" +
"CjAwMDAwMDAwNzkgMDAwMDAgbiAKMDAwMDAwMDE3MyAwMDAwMCBuIAowMDAwMDAwMzAxIDAw" +
"MDAwIG4gCjAwMDAwMDAzODAgMDAwMDAgbiAKdHJhaWxlcgo8PAogIC9TaXplIDYKICAvUm9v" +
"dCAxIDAgUgo+PgpzdGFydHhyZWYKNDkyCiUlRU9G"
);
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
//
// The workerSrc property shall be specified.
//
pdfjsLib.GlobalWorkerOptions.workerSrc = "../../node_modules/pdfjs-dist/build/pdf.worker.mjs";
<script id="script" type="module">
// atob() is used to convert base64 encoded PDF to binary-like data.
// (See also https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/
// Base64_encoding_and_decoding.)
var pdfData = atob(
'JVBERi0xLjcKCjEgMCBvYmogICUgZW50cnkgcG9pbnQKPDwKICAvVHlwZSAvQ2F0YWxvZwog' +
'IC9QYWdlcyAyIDAgUgo+PgplbmRvYmoKCjIgMCBvYmoKPDwKICAvVHlwZSAvUGFnZXMKICAv' +
'TWVkaWFCb3ggWyAwIDAgMjAwIDIwMCBdCiAgL0NvdW50IDEKICAvS2lkcyBbIDMgMCBSIF0K' +
'Pj4KZW5kb2JqCgozIDAgb2JqCjw8CiAgL1R5cGUgL1BhZ2UKICAvUGFyZW50IDIgMCBSCiAg' +
'L1Jlc291cmNlcyA8PAogICAgL0ZvbnQgPDwKICAgICAgL0YxIDQgMCBSIAogICAgPj4KICA+' +
'PgogIC9Db250ZW50cyA1IDAgUgo+PgplbmRvYmoKCjQgMCBvYmoKPDwKICAvVHlwZSAvRm9u' +
'dAogIC9TdWJ0eXBlIC9UeXBlMQogIC9CYXNlRm9udCAvVGltZXMtUm9tYW4KPj4KZW5kb2Jq' +
'Cgo1IDAgb2JqICAlIHBhZ2UgY29udGVudAo8PAogIC9MZW5ndGggNDQKPj4Kc3RyZWFtCkJU' +
'CjcwIDUwIFRECi9GMSAxMiBUZgooSGVsbG8sIHdvcmxkISkgVGoKRVQKZW5kc3RyZWFtCmVu' +
'ZG9iagoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDEwIDAwMDAwIG4g' +
'CjAwMDAwMDAwNzkgMDAwMDAgbiAKMDAwMDAwMDE3MyAwMDAwMCBuIAowMDAwMDAwMzAxIDAw' +
'MDAwIG4gCjAwMDAwMDAzODAgMDAwMDAgbiAKdHJhaWxlcgo8PAogIC9TaXplIDYKICAvUm9v' +
'dCAxIDAgUgo+PgpzdGFydHhyZWYKNDkyCiUlRU9G');
// Opening PDF by passing its binary data as a string. It is still preferable
// to use Uint8Array, but string or array-like structure will work too.
var loadingTask = pdfjsLib.getDocument({ data: pdfData });
var pdf = await loadingTask.promise;
// Fetch the first page.
var page = await pdf.getPage(1);
var scale = 1.5;
var viewport = page.getViewport({ scale: scale });
// Support HiDPI-screens.
var outputScale = window.devicePixelRatio || 1;
//
// The workerSrc property shall be specified.
//
pdfjsLib.GlobalWorkerOptions.workerSrc =
'../../node_modules/pdfjs-dist/build/pdf.worker.mjs';
// Prepare canvas using PDF page dimensions.
var canvas = document.getElementById("the-canvas");
var context = canvas.getContext("2d");
// Opening PDF by passing its binary data as a string. It is still preferable
// to use Uint8Array, but string or array-like structure will work too.
var loadingTask = pdfjsLib.getDocument({ data: pdfData, });
var pdf = await loadingTask.promise;
// Fetch the first page.
var page = await pdf.getPage(1);
var scale = 1.5;
var viewport = page.getViewport({ scale: scale, });
// Support HiDPI-screens.
var outputScale = window.devicePixelRatio || 1;
canvas.width = Math.floor(viewport.width * outputScale);
canvas.height = Math.floor(viewport.height * outputScale);
canvas.style.width = Math.floor(viewport.width) + "px";
canvas.style.height = Math.floor(viewport.height) + "px";
// Prepare canvas using PDF page dimensions.
var canvas = document.getElementById('the-canvas');
var context = canvas.getContext('2d');
var transform = outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null;
canvas.width = Math.floor(viewport.width * outputScale);
canvas.height = Math.floor(viewport.height * outputScale);
canvas.style.width = Math.floor(viewport.width) + "px";
canvas.style.height = Math.floor(viewport.height) + "px";
// Render PDF page into canvas context.
var renderContext = {
canvasContext: context,
transform,
viewport,
};
page.render(renderContext);
</script>
var transform = outputScale !== 1
? [outputScale, 0, 0, outputScale, 0, 0]
: null;
<hr />
<h2>JavaScript code:</h2>
<pre id="code"></pre>
<script>
document.getElementById("code").textContent = document.getElementById("script").text;
</script>
</body>
// Render PDF page into canvas context.
var renderContext = {
canvasContext: context,
transform,
viewport,
};
page.render(renderContext);
</script>
<hr>
<h2>JavaScript code:</h2>
<pre id="code"></pre>
<script>
document.getElementById('code').textContent =
document.getElementById('script').text;
</script>
</body>
</html>

View File

@ -1,134 +1,139 @@
<!doctype html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Previous/Next example</title>
</head>
<body>
<h1>'Previous/Next' example</h1>
<head>
<meta charset="UTF-8">
<title>Previous/Next example</title>
</head>
<body>
<div>
<button id="prev" type="button">Previous</button>
<button id="next" type="button">Next</button>
&nbsp; &nbsp;
<span>Page: <span id="page_num"></span> / <span id="page_count"></span></span>
</div>
<h1>'Previous/Next' example</h1>
<div>
<canvas id="the-canvas" style="border: 1px solid black; direction: ltr"></canvas>
</div>
<div>
<button id="prev">Previous</button>
<button id="next">Next</button>
&nbsp; &nbsp;
<span>Page: <span id="page_num"></span> / <span id="page_count"></span></span>
</div>
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
<div>
<canvas id="the-canvas" style="border: 1px solid black; direction: ltr;"></canvas>
</div>
<script id="script" type="module">
//
// If absolute URL from the remote server is provided, configure the CORS
// header on that server.
//
var url = "../../web/compressed.tracemonkey-pldi-09.pdf";
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
//
// In cases when the pdf.worker.js is located at the different folder than the
// PDF.js's one, or the PDF.js is executed via eval(), the workerSrc property
// shall be specified.
//
pdfjsLib.GlobalWorkerOptions.workerSrc = "../../node_modules/pdfjs-dist/build/pdf.worker.mjs";
<script id="script" type="module">
//
// If absolute URL from the remote server is provided, configure the CORS
// header on that server.
//
var url = '../../web/compressed.tracemonkey-pldi-09.pdf';
var pdfDoc = null,
pageNum = 1,
pageRendering = false,
pageNumPending = null,
scale = 0.8,
canvas = document.getElementById("the-canvas"),
ctx = canvas.getContext("2d");
//
// In cases when the pdf.worker.js is located at the different folder than the
// PDF.js's one, or the PDF.js is executed via eval(), the workerSrc property
// shall be specified.
//
pdfjsLib.GlobalWorkerOptions.workerSrc =
'../../node_modules/pdfjs-dist/build/pdf.worker.mjs';
/**
* Get page info from document, resize canvas accordingly, and render page.
* @param num Page number.
*/
function renderPage(num) {
pageRendering = true;
// Using promise to fetch the page
pdfDoc.getPage(num).then(function (page) {
var viewport = page.getViewport({ scale: scale });
// Support HiDPI-screens.
var outputScale = window.devicePixelRatio || 1;
var pdfDoc = null,
pageNum = 1,
pageRendering = false,
pageNumPending = null,
scale = 0.8,
canvas = document.getElementById('the-canvas'),
ctx = canvas.getContext('2d');
canvas.width = Math.floor(viewport.width * outputScale);
canvas.height = Math.floor(viewport.height * outputScale);
canvas.style.width = Math.floor(viewport.width) + "px";
canvas.style.height = Math.floor(viewport.height) + "px";
/**
* Get page info from document, resize canvas accordingly, and render page.
* @param num Page number.
*/
function renderPage(num) {
pageRendering = true;
// Using promise to fetch the page
pdfDoc.getPage(num).then(function(page) {
var viewport = page.getViewport({ scale: scale, });
// Support HiDPI-screens.
var outputScale = window.devicePixelRatio || 1;
var transform = outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null;
canvas.width = Math.floor(viewport.width * outputScale);
canvas.height = Math.floor(viewport.height * outputScale);
canvas.style.width = Math.floor(viewport.width) + "px";
canvas.style.height = Math.floor(viewport.height) + "px";
// Render PDF page into canvas context
var renderContext = {
canvasContext: ctx,
transform: transform,
viewport: viewport,
};
var renderTask = page.render(renderContext);
var transform = outputScale !== 1
? [outputScale, 0, 0, outputScale, 0, 0]
: null;
// Wait for rendering to finish
renderTask.promise.then(function () {
pageRendering = false;
if (pageNumPending !== null) {
// New page rendering is pending
renderPage(pageNumPending);
pageNumPending = null;
}
});
});
// Render PDF page into canvas context
var renderContext = {
canvasContext: ctx,
transform: transform,
viewport: viewport,
};
var renderTask = page.render(renderContext);
// Update page counters
document.getElementById("page_num").textContent = num;
}
/**
* If another page rendering in progress, waits until the rendering is
* finished. Otherwise, executes rendering immediately.
*/
function queueRenderPage(num) {
if (pageRendering) {
pageNumPending = num;
} else {
renderPage(num);
// Wait for rendering to finish
renderTask.promise.then(function () {
pageRendering = false;
if (pageNumPending !== null) {
// New page rendering is pending
renderPage(pageNumPending);
pageNumPending = null;
}
}
});
});
/**
* Displays previous page.
*/
function onPrevPage() {
if (pageNum <= 1) {
return;
}
pageNum--;
queueRenderPage(pageNum);
}
document.getElementById("prev").addEventListener("click", onPrevPage);
// Update page counters
document.getElementById('page_num').textContent = num;
}
/**
* Displays next page.
*/
function onNextPage() {
if (pageNum >= pdfDoc.numPages) {
return;
}
pageNum++;
queueRenderPage(pageNum);
}
document.getElementById("next").addEventListener("click", onNextPage);
/**
* If another page rendering in progress, waits until the rendering is
* finished. Otherwise, executes rendering immediately.
*/
function queueRenderPage(num) {
if (pageRendering) {
pageNumPending = num;
} else {
renderPage(num);
}
}
/**
* Asynchronously downloads PDF.
*/
var loadingTask = pdfjsLib.getDocument(url);
pdfDoc = await loadingTask.promise;
document.getElementById("page_count").textContent = pdfDoc.numPages;
/**
* Displays previous page.
*/
function onPrevPage() {
if (pageNum <= 1) {
return;
}
pageNum--;
queueRenderPage(pageNum);
}
document.getElementById('prev').addEventListener('click', onPrevPage);
// Initial/first page rendering
renderPage(pageNum);
</script>
</body>
/**
* Displays next page.
*/
function onNextPage() {
if (pageNum >= pdfDoc.numPages) {
return;
}
pageNum++;
queueRenderPage(pageNum);
}
document.getElementById('next').addEventListener('click', onNextPage);
/**
* Asynchronously downloads PDF.
*/
var loadingTask = pdfjsLib.getDocument(url);
pdfDoc = await loadingTask.promise;
document.getElementById('page_count').textContent = pdfDoc.numPages;
// Initial/first page rendering
renderPage(pageNum);
</script>
</body>
</html>

View File

@ -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;

View File

@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<!--
Copyright 2016 Mozilla Foundation
@ -16,13 +16,13 @@ limitations under the License.
-->
<html dir="ltr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>PDF.js viewer</title>
<link rel="stylesheet" href="../../node_modules/pdfjs-dist/web/pdf_viewer.css" />
<link rel="stylesheet" type="text/css" href="viewer.css" />
<link rel="stylesheet" href="../../node_modules/pdfjs-dist/web/pdf_viewer.css">
<link rel="stylesheet" type="text/css" href="viewer.css">
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
<script src="../../node_modules/pdfjs-dist/web/pdf_viewer.mjs" type="module"></script>
@ -43,15 +43,15 @@ limitations under the License.
</div>
<footer>
<button class="toolbarButton pageUp" title="Previous Page" id="previous" type="button"></button>
<button class="toolbarButton pageDown" title="Next Page" id="next" type="button"></button>
<button class="toolbarButton pageUp" title="Previous Page" id="previous"></button>
<button class="toolbarButton pageDown" title="Next Page" id="next"></button>
<input type="number" id="pageNumber" class="toolbarField pageNumber" value="1" size="4" min="1" />
<input type="number" id="pageNumber" class="toolbarField pageNumber" value="1" size="4" min="1">
<button class="toolbarButton zoomOut" title="Zoom Out" id="zoomOut" type="button"></button>
<button class="toolbarButton zoomIn" title="Zoom In" id="zoomIn" type="button"></button>
<button class="toolbarButton zoomOut" title="Zoom Out" id="zoomOut"></button>
<button class="toolbarButton zoomIn" title="Zoom In" id="zoomIn"></button>
</footer>
<script src="viewer.mjs" type="module"></script>
<script src="viewer.mjs" type="module"></script>
</body>
</html>

View File

@ -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.ResponseException) {
key = reason.missing
? "pdfjs-missing-file-error"
: "pdfjs-unexpected-response-error";
} else if (reason instanceof pdfjsLib.MissingPDFException) {
key = "pdfjs-missing-file-error";
} else if (reason instanceof pdfjsLib.UnexpectedResponseException) {
key = "pdfjs-unexpected-response-error";
}
self.l10n.get(key).then(msg => {
self.error(msg, { message: reason?.message });

9
examples/node/.eslintrc Normal file
View File

@ -0,0 +1,9 @@
{
"extends": [
"../.eslintrc"
],
"env": {
"node": true,
},
}

View File

@ -9,7 +9,9 @@ Install the dependencies and build the PDF.js library:
$ npm install
$ gulp dist-install
Run the example to convert the first page of a PDF file to a PNG image:
Install the Node canvas library and 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.mjs
$ node pdf2png.js

View File

@ -13,9 +13,41 @@
* 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;
@ -24,6 +56,8 @@ 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";
@ -35,6 +69,7 @@ const loadingTask = getDocument({
cMapUrl: CMAP_URL,
cMapPacked: CMAP_PACKED,
standardFontDataUrl: STANDARD_FONT_DATA_URL,
canvasFactory,
});
try {
@ -43,7 +78,6 @@ 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,
@ -57,7 +91,7 @@ try {
const renderTask = page.render(renderContext);
await renderTask.promise;
// Convert the canvas to an image buffer.
const image = canvasAndContext.canvas.toBuffer("image/png");
const image = canvasAndContext.canvas.toBuffer();
fs.writeFile("output.png", image, function (error) {
if (error) {
console.error("Error: " + error);

View File

@ -1,13 +1,14 @@
<!doctype html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<head>
<meta charset="UTF-8">
<title>Text-only PDF.js example</title>
<script src="../../node_modules/pdfjs-dist/build/pdf.mjs" type="module"></script>
<script src="pdf2svg.mjs" type="module"></script>
</head>
<body>
<p>Text-only PDF.js example</p>
<div id="pageContainer" style="display: inline-block; border: solid 1px black"></div>
</body>
</head>
<body>
<p>Text-only PDF.js example</p>
<div id="pageContainer" style="display: inline-block; border: solid 1px black;">
</div>
</body>
</html>

View File

@ -0,0 +1,9 @@
{
"extends": [
"../.eslintrc"
],
"env": {
"node": true,
},
}

View File

@ -1,11 +1,11 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>webpack example</title>
<script src="../../build/webpack/main.bundle.js"></script>
</head>
<body>
<canvas id="theCanvas"></canvas>
</body>
<head>
<meta charset="UTF-8">
<title>webpack example</title>
<script src="../../build/webpack/main.bundle.js"></script>
</head>
<body>
<canvas id="theCanvas"></canvas>
</body>
</html>

View File

@ -0,0 +1,22 @@
{
"extends": [
../../.eslintrc
],
"env": {
"webextensions": true
},
"plugins": [
"mozilla"
],
"parserOptions": {
"sourceType": "script"
},
"rules": {
"mozilla/import-globals": "error",
"no-var": "off",
},
}

View File

@ -16,16 +16,13 @@ limitations under the License.
"use strict";
var VIEWER_URL = chrome.runtime.getURL("content/web/viewer.html");
var VIEWER_URL = chrome.extension.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") {
@ -224,38 +221,3 @@ 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);
}

View File

@ -17,12 +17,13 @@ limitations under the License.
"use strict";
(function ExtensionRouterClosure() {
var VIEWER_URL = chrome.runtime.getURL("content/web/viewer.html");
var CRX_BASE_URL = chrome.runtime.getURL("/");
var VIEWER_URL = chrome.extension.getURL("content/web/viewer.html");
var CRX_BASE_URL = chrome.extension.getURL("/");
var schemes = [
"http",
"https",
"ftp",
"file",
"chrome-extension",
"blob",
@ -46,7 +47,6 @@ 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,50 +56,73 @@ limitations under the License.
return undefined;
}
function resolveViewerURL(originalUrl) {
if (originalUrl.startsWith(CRX_BASE_URL)) {
// 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) {
// This listener converts chrome-extension://.../http://...pdf to
// chrome-extension://.../content/web/viewer.html?file=http%3A%2F%2F...pdf
var url = parseExtensionURL(originalUrl);
var url = parseExtensionURL(details.url);
if (url) {
url = VIEWER_URL + "?file=" + url;
var i = originalUrl.indexOf("#");
var i = details.url.indexOf("#");
if (i > 0) {
url += originalUrl.slice(i);
url += details.url.slice(i);
}
return url;
}
}
return undefined;
}
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));
}
}
});
// 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 });
console.log("Redirecting " + details.url + " to " + url);
return { redirectUrl: url };
}
return undefined;
},
{ url: [{ urlPrefix: CRX_BASE_URL }] }
{
types: ["main_frame", "sub_frame"],
urls: schemes.map(function (scheme) {
// Format: "chrome-extension://[EXTENSIONID]/<scheme>*"
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);
}
}
);
console.log("Set up extension URL router.");
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,
});
}
localStorage.removeItem(key);
}
});
})();

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,6 +1,6 @@
{
"minimum_chrome_version": "103",
"manifest_version": 3,
"minimum_chrome_version": "88",
"manifest_version": 2,
"name": "PDF Viewer",
"version": "PDFJSSCRIPT_VERSION",
"description": "Uses HTML5 to display PDF files directly in the browser.",
@ -10,52 +10,61 @@
"16": "icon16.png"
},
"permissions": [
"alarms",
"declarativeNetRequestWithHostAccess",
"fileBrowserHandler",
"webRequest",
"webRequestBlocking",
"<all_urls>",
"tabs",
"webNavigation",
"storage"
],
"host_permissions": ["<all_urls>"],
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*", "file://*/*"],
"matches": ["http://*/*", "https://*/*", "ftp://*/*", "file://*/*"],
"run_at": "document_start",
"all_frames": true,
"css": ["contentstyle.css"],
"js": ["contentscript.js"]
}
],
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
},
"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"]
}
],
"storage": {
"managed_schema": "preferences_schema.json"
},
"options_ui": {
"page": "options/options.html"
"page": "options/options.html",
"chrome_style": true
},
"options_page": "options/options.html",
"background": {
"service_worker": "background.js"
"page": "pdfHandler.html"
},
"page_action": {
"default_icon": {
"19": "icon19.png",
"38": "icon38.png"
},
"default_title": "Show PDF URL",
"default_popup": "pageActionPopup.html"
},
"incognito": "split",
"web_accessible_resources": [
{
"resources": [
"content/web/viewer.html",
"http:/*",
"https:/*",
"file:/*",
"chrome-extension:/*",
"blob:*",
"data:*",
"filesystem:/*",
"drive:*"
],
"matches": ["<all_urls>"],
"extension_ids": ["*"]
}
"content/web/viewer.html",
"http:/*",
"https:/*",
"ftp:/*",
"file:/*",
"chrome-extension:/*",
"blob:*",
"data:*",
"filesystem:/*",
"drive:*"
]
}

View File

@ -13,14 +13,10 @@ 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.
*/
"use strict";
/* eslint strict: ["error", "function"] */
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;
}
(function () {
"use strict";
var storageLocal = chrome.storage.local;
var storageSync = chrome.storage.sync;
@ -41,12 +37,16 @@ chrome.runtime.onInstalled.addListener(({ reason }) => {
});
});
async function getStorageNames(callback) {
function getStorageNames(callback) {
var x = new XMLHttpRequest();
var schema_location = chrome.runtime.getManifest().storage.managed_schema;
var res = await fetch(chrome.runtime.getURL(schema_location));
var storageManifest = await res.json();
var storageKeys = Object.keys(storageManifest.properties);
callback(storageKeys);
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();
}
// Save |values| to storage.sync and delete the values with that key from
@ -150,4 +150,4 @@ chrome.runtime.onInstalled.addListener(({ reason }) => {
}
);
}
});
})();

View File

@ -15,171 +15,166 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<html>
<head>
<meta charset="utf-8" />
<title>PDF.js viewer options</title>
<style>
body {
min-width: 400px; /* a page at the settings page is at least 400px wide */
margin: 14px 17px; /* already added by default in Chrome 40.0.2212.0 */
}
.settings-row {
margin: 1em 0;
}
.checkbox label {
display: inline-flex;
align-items: center;
}
.checkbox label input {
flex-shrink: 0;
}
</style>
</head>
<body>
<div id="settings-boxes"></div>
<button id="reset-button" type="button">Restore default settings</button>
<head>
<meta charset="utf-8">
<title>PDF.js viewer options</title>
<style>
/* TODO: Remove as much custom CSS as possible - crbug.com/446511 */
body {
min-width: 400px; /* a page at the settings page is at least 400px wide */
margin: 14px 17px; /* already added by default in Chrome 40.0.2212.0 */
}
.settings-row {
margin: 0.65em 0;
}
</style>
</head>
<body>
<div id="settings-boxes"></div>
<button id="reset-button">Restore default settings</button>
<template id="checkbox-template">
<div class="settings-row checkbox">
<label>
<input type="checkbox" />
<span></span>
</label>
</div>
</template>
<template id="checkbox-template">
<!-- Chromium's style: //src/extensions/renderer/resources/extension.css -->
<div class="checkbox">
<label>
<input type="checkbox">
<span></span>
</label>
</div>
</template>
<template id="viewerCssTheme-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="0">Use system theme</option>
<option value="1">Light theme</option>
<option value="2">Dark theme</option>
</select>
</label>
</div>
</template>
<template id="viewerCssTheme-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="0">Use system theme</option>
<option value="1">Light theme</option>
<option value="2">Dark theme</option>
</select>
</label>
</div>
</template>
<template id="viewOnLoad-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="-1">Default</option>
<option value="0">Show previous position</option>
<option value="1">Show initial position</option>
</select>
</label>
</div>
</template>
<template id="viewOnLoad-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="-1">Default</option>
<option value="0">Show previous position</option>
<option value="1">Show initial position</option>
</select>
</label>
</div>
</template>
<template id="defaultZoomValue-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="auto" selected="selected">Automatic Zoom</option>
<option value="page-actual">Actual Size</option>
<option value="page-fit">Page Fit</option>
<option value="page-width">Page Width</option>
<option value="custom" class="custom-zoom" hidden></option>
<option value="50">50%</option>
<option value="75">75%</option>
<option value="100">100%</option>
<option value="125">125%</option>
<option value="150">150%</option>
<option value="200">200%</option>
<option value="300">300%</option>
<option value="400">400%</option>
</select>
</label>
</div>
</template>
<template id="defaultZoomValue-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="auto" selected="selected">Automatic Zoom</option>
<option value="page-actual">Actual Size</option>
<option value="page-fit">Page Fit</option>
<option value="page-width">Page Width</option>
<option value="custom" class="custom-zoom" hidden></option>
<option value="50">50%</option>
<option value="75">75%</option>
<option value="100">100%</option>
<option value="125">125%</option>
<option value="150">150%</option>
<option value="200">200%</option>
<option value="300">300%</option>
<option value="400">400%</option>
</select>
</label>
</div>
</template>
<template id="sidebarViewOnLoad-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="-1">Default</option>
<option value="0">Do not show sidebar</option>
<option value="1">Show thumbnails in sidebar</option>
<option value="2">Show document outline in sidebar</option>
<option value="3">Show attachments in sidebar</option>
</select>
</label>
</div>
</template>
<template id="sidebarViewOnLoad-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="-1">Default</option>
<option value="0">Do not show sidebar</option>
<option value="1">Show thumbnails in sidebar</option>
<option value="2">Show document outline in sidebar</option>
<option value="3">Show attachments in sidebar</option>
</select>
</label>
</div>
</template>
<template id="cursorToolOnLoad-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="0">Text selection tool</option>
<option value="1">Hand tool</option>
</select>
</label>
</div>
</template>
<template id="cursorToolOnLoad-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="0">Text selection tool</option>
<option value="1">Hand tool</option>
</select>
</label>
</div>
</template>
<template id="textLayerMode-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="0">Disable text selection</option>
<option value="1">Enable text selection</option>
</select>
</label>
</div>
</template>
<template id="textLayerMode-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="0">Disable text selection</option>
<option value="1">Enable text selection</option>
</select>
</label>
</div>
</template>
<template id="externalLinkTarget-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="0">Default</option>
<option value="1">Current window/tab</option>
<option value="2">New window/tab</option>
<option value="3">Parent window/tab</option>
<option value="4">Top window/tab</option>
</select>
</label>
</div>
</template>
<template id="externalLinkTarget-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="0">Default</option>
<option value="1">Current window/tab</option>
<option value="2">New window/tab</option>
<option value="3">Parent window/tab</option>
<option value="4">Top window/tab</option>
</select>
</label>
</div>
</template>
<template id="scrollModeOnLoad-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="-1">Default</option>
<option value="3">Page scrolling</option>
<option value="0">Vertical scrolling</option>
<option value="1">Horizontal scrolling</option>
<option value="2">Wrapped scrolling</option>
</select>
</label>
</div>
</template>
<template id="scrollModeOnLoad-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="-1">Default</option>
<option value="3">Page scrolling</option>
<option value="0">Vertical scrolling</option>
<option value="1">Horizontal scrolling</option>
<option value="2">Wrapped scrolling</option>
</select>
</label>
</div>
</template>
<template id="spreadModeOnLoad-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="-1">Default</option>
<option value="0">No spreads</option>
<option value="1">Odd spreads</option>
<option value="2">Even spreads</option>
</select>
</label>
</div>
</template>
<template id="spreadModeOnLoad-template">
<div class="settings-row">
<label>
<span></span>
<select>
<option value="-1">Default</option>
<option value="0">No spreads</option>
<option value="1">Odd spreads</option>
<option value="2">Even spreads</option>
</select>
</label>
</div>
</template>
<script src="options.js"></script>
</body>
<script src="options.js"></script>
</body>
</html>

View File

@ -0,0 +1,45 @@
/*
Copyright 2014 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.
*/
"use strict";
(function PageActionClosure() {
/**
* @param {number} tabId - ID of tab where the page action will be shown.
* @param {string} url - URL to be displayed in page action.
*/
function showPageAction(tabId, displayUrl) {
// rewriteUrlClosure in viewer.js ensures that the URL looks like
// chrome-extension://[extensionid]/http://example.com/file.pdf
var url = /^chrome-extension:\/\/[a-p]{32}\/([^#]+)/.exec(displayUrl);
if (url) {
url = url[1];
chrome.pageAction.setPopup({
tabId,
popup: "/pageAction/popup.html?file=" + encodeURIComponent(url),
});
chrome.pageAction.show(tabId);
} else {
console.log("Unable to get PDF url from " + displayUrl);
}
}
chrome.runtime.onMessage.addListener(function (message, sender) {
if (message === "showPageAction" && sender.tab) {
showPageAction(sender.tab.id, sender.tab.url);
}
});
})();

View File

@ -0,0 +1,44 @@
<!doctype html>
<!--
Copyright 2012 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.
-->
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
html {
/* maximum width of popup as defined in Chromium's source code as kMaxWidth
//src/chrome/browser/ui/views/extensions/extension_popup.cc
//src/chrome/browser/ui/gtk/extensions/extension_popup_gtk.cc
*/
width: 800px;
/* in case Chromium decides to lower the value of kMaxWidth */
max-width: 100%;
margin: 0;
padding: 0;
}
body {
box-sizing: border-box;
margin: 0;
padding: 5px;
width: 100%;
}
</style>
</head>
<body contentEditable="plaintext-only" spellcheck="false">
<script src="popup.js"></script>
</body>
</html>

View File

@ -0,0 +1,25 @@
/* Copyright 2012 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.
*/
"use strict";
var url = location.search.match(/[&?]file=([^&]+)/i);
if (url) {
url = decodeURIComponent(url[1]);
document.body.textContent = url;
// Set cursor to end of the content-editable section.
window.getSelection().selectAllChildren(document.body);
window.getSelection().collapseToEnd();
}

View File

@ -0,0 +1,102 @@
/*
Copyright 2014 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.
*/
/* eslint strict: ["error", "function"] */
/* import-globals-from pdfHandler.js */
(function () {
"use strict";
if (!chrome.fileBrowserHandler) {
// Not on Chromium OS, bail out
return;
}
chrome.fileBrowserHandler.onExecute.addListener(onExecuteFileBrowserHandler);
/**
* Invoked when "Open with PDF Viewer" is chosen in the File browser.
*
* @param {string} id File browser action ID as specified in
* manifest.json
* @param {Object} details Object of type FileHandlerExecuteEventDetails
*/
function onExecuteFileBrowserHandler(id, details) {
if (id !== "open-as-pdf") {
return;
}
var fileEntries = details.entries;
// "tab_id" is the currently documented format, but it is inconsistent with
// the other Chrome APIs that use "tabId" (http://crbug.com/179767)
var tabId = details.tab_id || details.tabId;
if (tabId > 0) {
chrome.tabs.get(tabId, function (tab) {
openViewer(tab && tab.windowId, fileEntries);
});
} else {
// Re-use existing window, if available.
chrome.windows.getLastFocused(function (chromeWindow) {
var windowId = chromeWindow && chromeWindow.id;
if (windowId) {
chrome.windows.update(windowId, { focused: true });
}
openViewer(windowId, fileEntries);
});
}
}
/**
* Open the PDF Viewer for the given list of PDF files.
*
* @param {number} windowId
* @param {Array} fileEntries List of Entry objects (HTML5 FileSystem API)
*/
function openViewer(windowId, fileEntries) {
if (!fileEntries.length) {
return;
}
var fileEntry = fileEntries.shift();
var url = fileEntry.toURL();
// Use drive: alias to get shorter (more human-readable) URLs.
url = url.replace(
/^filesystem:chrome-extension:\/\/[a-p]{32}\/external\//,
"drive:"
);
url = getViewerURL(url);
if (windowId) {
chrome.tabs.create(
{
windowId,
active: true,
url,
},
function () {
openViewer(windowId, fileEntries);
}
);
} else {
chrome.windows.create(
{
type: "normal",
focused: true,
url,
},
function (chromeWindow) {
openViewer(chromeWindow.id, fileEntries);
}
);
}
}
})();

View File

@ -0,0 +1,24 @@
<!doctype html>
<!--
Copyright 2012 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.
-->
<script src="options/migration.js"></script>
<script src="preserve-referer.js"></script>
<script src="pdfHandler.js"></script>
<script src="extension-router.js"></script>
<script src="pdfHandler-vcros.js"></script>
<script src="pageAction/background.js"></script>
<script src="suppress-update.js"></script>
<script src="telemetry.js"></script>

View File

@ -13,256 +13,11 @@ 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.
*/
/* globals canRequestBody */ // From preserve-referer.js
/* import-globals-from preserve-referer.js */
"use strict";
var VIEWER_URL = chrome.runtime.getURL("content/web/viewer.html");
// Use in-memory storage to ensure that the DNR rules have been registered at
// least once per session. runtime.onInstalled would have been the most fitting
// event to ensure that, except there are cases where it does not fire when
// needed. E.g. in incognito mode: https://issues.chromium.org/issues/41029550
chrome.storage.session.get({ hasPdfRedirector: false }, async items => {
if (items?.hasPdfRedirector) {
return;
}
const rules = await chrome.declarativeNetRequest.getDynamicRules();
if (rules.length) {
// Dynamic rules persist across extension updates. We don't expect other
// dynamic rules, so just remove them all.
await chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: rules.map(r => r.id),
});
}
await registerPdfRedirectRule();
// Only set the flag in the end, so that we know for sure that all
// asynchronous initialization logic has run. If not, then we will run the
// logic again at the next background wakeup.
chrome.storage.session.set({ hasPdfRedirector: true });
});
/**
* Registers declarativeNetRequest rules to redirect PDF requests to the viewer.
* The caller should clear any previously existing dynamic DNR rules.
*
* The logic here is the declarative version of the runtime logic in the
* webRequest.onHeadersReceived implementation at
* https://github.com/mozilla/pdf.js/blob/0676ea19cf17023ec8c2d6ad69a859c345c01dc1/extensions/chromium/pdfHandler.js#L34-L152
*/
async function registerPdfRedirectRule() {
// "allow" means to ignore rules (from this extension) with lower priority.
const ACTION_IGNORE_OTHER_RULES = { type: "allow" };
// Redirect to viewer. The rule condition is expected to specify regexFilter
// that matches the full request URL.
const ACTION_REDIRECT_TO_VIEWER = {
type: "redirect",
redirect: {
// DNR does not support transformations such as encodeURIComponent on the
// match, so we just concatenate the URL as is without modifications.
// TODO: use "?file=\\0" when DNR supports transformations as proposed at
// https://github.com/w3c/webextensions/issues/636#issuecomment-2165978322
regexSubstitution: VIEWER_URL + "?DNR:\\0",
},
};
// Rules in order of priority (highest priority rule first).
// The required "id" fields will be auto-generated later.
const addRules = [
{
// Do not redirect for URLs containing pdfjs.action=download.
condition: {
urlFilter: "pdfjs.action=download",
resourceTypes: ["main_frame", "sub_frame"],
},
action: ACTION_IGNORE_OTHER_RULES,
},
{
// Redirect local PDF files if isAllowedFileSchemeAccess is true. No-op
// otherwise and then handled by webNavigation.onBeforeNavigate below.
condition: {
regexFilter: "^file://.*\\.pdf$",
resourceTypes: ["main_frame", "sub_frame"],
},
action: ACTION_REDIRECT_TO_VIEWER,
},
{
// Respect the Content-Disposition:attachment header in sub_frame. But:
// Display the PDF viewer regardless of the Content-Disposition header if
// the file is displayed in the main frame, since most often users want to
// view a PDF, and servers are often misconfigured.
condition: {
urlFilter: "*",
resourceTypes: ["sub_frame"], // Note: no main_frame, handled below.
responseHeaders: [
{
header: "content-disposition",
values: ["attachment*"],
},
],
},
action: ACTION_IGNORE_OTHER_RULES,
},
{
// If the query string contains "=download", do not unconditionally force
// viewer to open the PDF, but first check whether the Content-Disposition
// header specifies an attachment. This allows sites like Google Drive to
// operate correctly (#6106).
condition: {
urlFilter: "=download",
resourceTypes: ["main_frame"], // No sub_frame, was handled before.
responseHeaders: [
{
header: "content-disposition",
values: ["attachment*"],
},
],
},
action: ACTION_IGNORE_OTHER_RULES,
},
{
// Regular http(s) PDF requests.
condition: {
regexFilter: "^.*$",
// The viewer does not have the original request context and issues a
// GET request. The original response to POST requests is unavailable.
excludedRequestMethods: ["post"],
resourceTypes: ["main_frame", "sub_frame"],
responseHeaders: [
{
header: "content-type",
values: ["application/pdf", "application/pdf;*"],
},
],
},
action: ACTION_REDIRECT_TO_VIEWER,
},
{
// Wrong MIME-type, but a PDF file according to the file name in the URL.
condition: {
regexFilter: "^.*\\.pdf\\b.*$",
// The viewer does not have the original request context and issues a
// GET request. The original response to POST requests is unavailable.
excludedRequestMethods: ["post"],
resourceTypes: ["main_frame", "sub_frame"],
responseHeaders: [
{
header: "content-type",
values: ["application/octet-stream", "application/octet-stream;*"],
},
],
},
action: ACTION_REDIRECT_TO_VIEWER,
},
{
// Wrong MIME-type, but a PDF file according to Content-Disposition.
condition: {
regexFilter: "^.*$",
// The viewer does not have the original request context and issues a
// GET request. The original response to POST requests is unavailable.
excludedRequestMethods: ["post"],
resourceTypes: ["main_frame", "sub_frame"],
responseHeaders: [
{
header: "content-disposition",
values: ["*.pdf", '*.pdf"*', "*.pdf'*"],
},
],
// We only want to match by content-disposition if Content-Type is set
// to application/octet-stream. The responseHeaders condition is a
// logical OR instead of AND, so to simulate the AND condition we use
// the double negation of excludedResponseHeaders + excludedValues.
// This matches any request whose content-type header is set and not
// "application/octet-stream". It will also match if "content-type" is
// not set, but we are okay with that since the browser would usually
// try to sniff the MIME type in that case.
excludedResponseHeaders: [
{
header: "content-type",
excludedValues: [
"application/octet-stream",
"application/octet-stream;*",
],
},
],
},
action: ACTION_REDIRECT_TO_VIEWER,
},
];
for (const [i, rule] of addRules.entries()) {
// id must be unique and at least 1, but i starts at 0. So add +1.
rule.id = i + 1;
rule.priority = addRules.length - i;
}
try {
// Note: condition.responseHeaders is only supported in Chrome 128+, but
// does not trigger errors in Chrome 123 - 127 as explained at:
// https://github.com/w3c/webextensions/issues/638#issuecomment-2181124486
// We need to detect this and avoid registering rules, because otherwise all
// requests are redirected to the viewer instead of just PDF requests,
// because Chrome accepts rules while ignoring the responseHeaders condition
// - also reported at https://crbug.com/347186592
if (!(await isHeaderConditionSupported())) {
throw new Error("DNR responseHeaders condition is not supported.");
}
await chrome.declarativeNetRequest.updateDynamicRules({ addRules });
} catch (e) {
// When we do not register DNR rules for any reason, fall back to catching
// PDF documents via maybeRenderPdfDoc in contentscript.js.
console.error("Failed to register rules to redirect PDF requests.");
console.error(e);
}
}
// For the source and explanation of this logic, see
// https://github.com/w3c/webextensions/issues/638#issuecomment-2181124486
async function isHeaderConditionSupported() {
const ruleId = 123456; // Some rule ID that is not already used elsewhere.
try {
// Throws synchronously if not supported.
await chrome.declarativeNetRequest.updateSessionRules({
addRules: [
{
id: ruleId,
condition: {
responseHeaders: [{ header: "whatever" }],
urlFilter: "|does_not_match_anything",
},
action: { type: "block" },
},
],
});
} catch {
return false; // responseHeaders condition not supported.
}
// Chrome may recognize the properties but have the implementation behind a
// flag. When the implementation is disabled, validation is skipped too.
try {
await chrome.declarativeNetRequest.updateSessionRules({
removeRuleIds: [ruleId],
addRules: [
{
id: ruleId,
condition: {
responseHeaders: [],
urlFilter: "|does_not_match_anything",
},
action: { type: "block" },
},
],
});
return false; // Validation skipped = feature disabled.
} catch {
return true; // Validation worked = feature enabled.
} finally {
await chrome.declarativeNetRequest.updateSessionRules({
removeRuleIds: [ruleId],
});
}
}
var VIEWER_URL = chrome.extension.getURL("content/web/viewer.html");
function getViewerURL(pdf_url) {
// |pdf_url| may contain a fragment such as "#page=2". That should be passed
@ -276,42 +31,174 @@ function getViewerURL(pdf_url) {
return VIEWER_URL + "?file=" + encodeURIComponent(pdf_url) + hash;
}
// If the user has not granted access to file:-URLs, then declarativeNetRequest
// will not catch the request. It is still visible through the webNavigation
// API though, and we can replace the tab with the viewer.
// The viewer will detect that it has no access to file:-URLs, and prompt the
// user to activate file permissions.
chrome.webNavigation.onBeforeNavigate.addListener(
function (details) {
// Note: pdfjs.action=download is not checked here because that code path
// is not reachable for local files through the viewer when we do not have
// file:-access.
if (details.frameId === 0) {
chrome.extension.isAllowedFileSchemeAccess(function (isAllowedAccess) {
if (isAllowedAccess) {
// Expected to be handled by DNR. Don't do anything.
return;
}
/**
* @param {Object} details First argument of the webRequest.onHeadersReceived
* event. The property "url" is read.
* @returns {boolean} True if the PDF file should be downloaded.
*/
function isPdfDownloadable(details) {
if (details.url.includes("pdfjs.action=download")) {
return true;
}
// Display the PDF viewer regardless of the Content-Disposition header if the
// file is displayed in the main frame, since most often users want to view
// a PDF, and servers are often misconfigured.
// If the query string contains "=download", do not unconditionally force the
// viewer to open the PDF, but first check whether the Content-Disposition
// header specifies an attachment. This allows sites like Google Drive to
// operate correctly (#6106).
if (details.type === "main_frame" && !details.url.includes("=download")) {
return false;
}
var cdHeader =
details.responseHeaders &&
getHeaderFromHeaders(details.responseHeaders, "content-disposition");
return cdHeader && /^attachment/i.test(cdHeader.value);
}
/**
* Get the header from the list of headers for a given name.
* @param {Array} headers responseHeaders of webRequest.onHeadersReceived
* @returns {undefined|{name: string, value: string}} The header, if found.
*/
function getHeaderFromHeaders(headers, headerName) {
for (const header of headers) {
if (header.name.toLowerCase() === headerName) {
return header;
}
}
return undefined;
}
/**
* Check if the request is a PDF file.
* @param {Object} details First argument of the webRequest.onHeadersReceived
* event. The properties "responseHeaders" and "url"
* are read.
* @returns {boolean} True if the resource is a PDF file.
*/
function isPdfFile(details) {
var header = getHeaderFromHeaders(details.responseHeaders, "content-type");
if (header) {
var headerValue = header.value.toLowerCase().split(";", 1)[0].trim();
if (headerValue === "application/pdf") {
return true;
}
if (headerValue === "application/octet-stream") {
if (details.url.toLowerCase().indexOf(".pdf") > 0) {
return true;
}
var cdHeader = getHeaderFromHeaders(
details.responseHeaders,
"content-disposition"
);
if (cdHeader && /\.pdf(["']|$)/i.test(cdHeader.value)) {
return true;
}
}
}
return false;
}
/**
* Takes a set of headers, and set "Content-Disposition: attachment".
* @param {Object} details First argument of the webRequest.onHeadersReceived
* event. The property "responseHeaders" is read and
* modified if needed.
* @returns {Object|undefined} The return value for the onHeadersReceived event.
* Object with key "responseHeaders" if the headers
* have been modified, undefined otherwise.
*/
function getHeadersWithContentDispositionAttachment(details) {
var headers = details.responseHeaders;
var cdHeader = getHeaderFromHeaders(headers, "content-disposition");
if (!cdHeader) {
cdHeader = { name: "Content-Disposition" };
headers.push(cdHeader);
}
if (!/^attachment/i.test(cdHeader.value)) {
cdHeader.value = "attachment" + cdHeader.value.replace(/^[^;]+/i, "");
return { responseHeaders: headers };
}
return undefined;
}
chrome.webRequest.onHeadersReceived.addListener(
function (details) {
if (details.method !== "GET") {
// Don't intercept POST requests until http://crbug.com/104058 is fixed.
return undefined;
}
if (!isPdfFile(details)) {
return undefined;
}
if (isPdfDownloadable(details)) {
// Force download by ensuring that Content-Disposition: attachment is set
return getHeadersWithContentDispositionAttachment(details);
}
var viewerUrl = getViewerURL(details.url);
// Implemented in preserve-referer.js
saveReferer(details);
return { redirectUrl: viewerUrl };
},
{
urls: ["<all_urls>"],
types: ["main_frame", "sub_frame"],
},
["blocking", "responseHeaders"]
);
chrome.webRequest.onBeforeRequest.addListener(
function (details) {
if (isPdfDownloadable(details)) {
return undefined;
}
var viewerUrl = getViewerURL(details.url);
return { redirectUrl: viewerUrl };
},
{
urls: ["file://*/*.pdf", "file://*/*.PDF"],
types: ["main_frame", "sub_frame"],
},
["blocking"]
);
chrome.extension.isAllowedFileSchemeAccess(function (isAllowedAccess) {
if (isAllowedAccess) {
return;
}
// If the user has not granted access to file:-URLs, then the webRequest API
// will not catch the request. It is still visible through the webNavigation
// API though, and we can replace the tab with the viewer.
// The viewer will detect that it has no access to file:-URLs, and prompt the
// user to activate file permissions.
chrome.webNavigation.onBeforeNavigate.addListener(
function (details) {
if (details.frameId === 0 && !isPdfDownloadable(details)) {
chrome.tabs.update(details.tabId, {
url: getViewerURL(details.url),
});
});
}
},
{
url: [
{
urlPrefix: "file://",
pathSuffix: ".pdf",
},
{
urlPrefix: "file://",
pathSuffix: ".PDF",
},
],
}
},
{
url: [
{
urlPrefix: "file://",
pathSuffix: ".pdf",
},
{
urlPrefix: "file://",
pathSuffix: ".PDF",
},
],
}
);
);
});
chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
if (message && message.action === "getParentOrigin") {
@ -358,11 +245,6 @@ chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
url,
});
}
return undefined;
}
if (message && message.action === "canRequestBody") {
sendResponse(canRequestBody(sender.tab.id, sender.frameId));
return undefined;
}
return undefined;
});

View File

@ -51,35 +51,7 @@
"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
},
"enableUpdatedAddImage": {
"enableML": {
"type": "boolean",
"default": false
},
@ -102,13 +74,21 @@
"description": "Whether to allow execution of active content (JavaScript) by PDF files.",
"default": false
},
"enableHighlightEditor": {
"type": "boolean",
"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"
"default": "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F"
},
"enableStampEditor": {
"type": "boolean",
"default": true
},
"disableRange": {
"title": "Disable range requests",
@ -176,10 +156,6 @@
"enum": [-1, 0, 3, 15],
"default": 0
},
"capCanvasAreaFactor": {
"type": "integer",
"default": 200
},
"enablePermissions": {
"type": "boolean",
"default": false
@ -232,21 +208,6 @@
"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
}
}
}

View File

@ -13,14 +13,20 @@ 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.
*/
/* import-globals-from pdfHandler.js */
/* exported saveReferer */
"use strict";
/**
* This file is one part of the Referer persistency implementation. The other
* part resides in chromecom.js.
*
* This file collects Referer headers for every http(s) request, and temporarily
* stores the request headers in a dictionary, for REFERRER_IN_MEMORY_TIME ms.
* This file collects request headers for every http(s) request, and temporarily
* stores the request headers in a dictionary. Upon completion of the request
* (success or failure), the headers are discarded.
* pdfHandler.js will call saveReferer(details) when it is about to redirect to
* the viewer. Upon calling saveReferer, the Referer header is extracted from
* the request headers and saved.
*
* When the viewer is opened, it opens a port ("chromecom-referrer"). This port
* is used to set up the webRequest listeners that stick the Referer headers to
@ -30,64 +36,49 @@ limitations under the License.
* See setReferer in chromecom.js for more explanation of this logic.
*/
/* exported canRequestBody */ // Used in pdfHandler.js
// Remembers the request headers for every http(s) page request for the duration
// of the request.
var g_requestHeaders = {};
// g_referrers[tabId][frameId] = referrer of PDF frame.
var g_referrers = {};
var g_referrerTimers = {};
// The background script will eventually suspend after 30 seconds of inactivity.
// This can be delayed when extension events are firing. To prevent the data
// from being kept in memory for too long, cap the data duration to 5 minutes.
var REFERRER_IN_MEMORY_TIME = 300000;
// g_postRequests[tabId] = Set of frameId that were loaded via POST.
var g_postRequests = {};
var rIsReferer = /^referer$/i;
chrome.webRequest.onSendHeaders.addListener(
function saveReferer(details) {
const { tabId, frameId, requestHeaders, method } = details;
g_referrers[tabId] ??= {};
g_referrers[tabId][frameId] = requestHeaders.find(h =>
rIsReferer.test(h.name)
)?.value;
setCanRequestBody(tabId, frameId, method !== "GET");
forgetReferrerEventually(tabId);
},
{ urls: ["*://*/*"], types: ["main_frame", "sub_frame"] },
["requestHeaders", "extraHeaders"]
);
function forgetReferrerEventually(tabId) {
if (g_referrerTimers[tabId]) {
clearTimeout(g_referrerTimers[tabId]);
(function () {
var requestFilter = {
urls: ["*://*/*"],
types: ["main_frame", "sub_frame"],
};
chrome.webRequest.onSendHeaders.addListener(
function (details) {
g_requestHeaders[details.requestId] = details.requestHeaders;
},
requestFilter,
["requestHeaders", "extraHeaders"]
);
chrome.webRequest.onBeforeRedirect.addListener(forgetHeaders, requestFilter);
chrome.webRequest.onCompleted.addListener(forgetHeaders, requestFilter);
chrome.webRequest.onErrorOccurred.addListener(forgetHeaders, requestFilter);
function forgetHeaders(details) {
delete g_requestHeaders[details.requestId];
}
g_referrerTimers[tabId] = setTimeout(() => {
delete g_referrers[tabId];
delete g_referrerTimers[tabId];
delete g_postRequests[tabId];
}, REFERRER_IN_MEMORY_TIME);
}
})();
// Keeps track of whether a document in tabId + frameId is loaded through a
// POST form submission. Although this logic has nothing to do with referrer
// tracking, it is still here to enable re-use of the webRequest listener above.
function setCanRequestBody(tabId, frameId, isPOST) {
if (isPOST) {
g_postRequests[tabId] ??= new Set();
g_postRequests[tabId].add(frameId);
} else {
g_postRequests[tabId]?.delete(frameId);
/**
* @param {object} details - onHeadersReceived event data.
*/
function saveReferer(details) {
var referer =
g_requestHeaders[details.requestId] &&
getHeaderFromHeaders(g_requestHeaders[details.requestId], "referer");
referer = (referer && referer.value) || "";
if (!g_referrers[details.tabId]) {
g_referrers[details.tabId] = {};
}
g_referrers[details.tabId][details.frameId] = referer;
}
function canRequestBody(tabId, frameId) {
// Returns true unless the frame is known to be loaded through a POST request.
// If the background suspends, the information may be lost. This is acceptable
// because the information is only potentially needed shortly after document
// load, by contentscript.js.
return !g_postRequests[tabId]?.has(frameId);
}
chrome.tabs.onRemoved.addListener(function (tabId) {
delete g_referrers[tabId];
});
// This method binds a webRequest event handler which adds the Referer header
// to matching PDF resource requests (only if the Referer is non-empty). The
@ -96,13 +87,15 @@ chrome.runtime.onConnect.addListener(function onReceivePort(port) {
if (port.name !== "chromecom-referrer") {
return;
}
// Note: sender.frameId is only set in Chrome 41+.
if (!("frameId" in port.sender)) {
port.disconnect();
return;
}
var tabId = port.sender.tab.id;
var frameId = port.sender.frameId;
var dnrRequestId;
// If the PDF is viewed for the first time, then the referer will be set here.
// Note: g_referrers could be empty if the background script was suspended by
// the browser. In that case, chromecom.js may send us the referer (below).
var referer = (g_referrers[tabId] && g_referrers[tabId][frameId]) || "";
port.onMessage.addListener(function (data) {
// If the viewer was opened directly (without opening a PDF URL first), then
@ -111,49 +104,80 @@ chrome.runtime.onConnect.addListener(function onReceivePort(port) {
if (data.referer) {
referer = data.referer;
}
dnrRequestId = data.dnrRequestId;
setStickyReferrer(dnrRequestId, tabId, data.requestUrl, referer, () => {
// Acknowledge the message, and include the latest referer for this frame.
port.postMessage(referer);
});
chrome.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
if (referer) {
// Only add a blocking request handler if the referer has to be rewritten.
chrome.webRequest.onBeforeSendHeaders.addListener(
onBeforeSendHeaders,
{
urls: [data.requestUrl],
types: ["xmlhttprequest"],
tabId,
},
["blocking", "requestHeaders", "extraHeaders"]
);
}
// Acknowledge the message, and include the latest referer for this frame.
port.postMessage(referer);
});
// The port is only disconnected when the other end reloads.
port.onDisconnect.addListener(function () {
unsetStickyReferrer(dnrRequestId);
if (g_referrers[tabId]) {
delete g_referrers[tabId][frameId];
}
chrome.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
chrome.webRequest.onHeadersReceived.removeListener(exposeOnHeadersReceived);
});
});
function setStickyReferrer(dnrRequestId, tabId, url, referer, callback) {
if (!referer) {
unsetStickyReferrer(dnrRequestId);
callback();
return;
}
const rule = {
id: dnrRequestId,
condition: {
urlFilter: `|${url}|`,
// The viewer and background are presumed to have the same origin:
initiatorDomains: [location.hostname], // = chrome.runtime.id.
resourceTypes: ["xmlhttprequest"],
tabIds: [tabId],
// Expose some response headers for fetch API calls from PDF.js;
// This is a work-around for https://crbug.com/784528
chrome.webRequest.onHeadersReceived.addListener(
exposeOnHeadersReceived,
{
urls: ["https://*/*"],
types: ["xmlhttprequest"],
tabId,
},
action: {
type: "modifyHeaders",
requestHeaders: [{ operation: "set", header: "referer", value: referer }],
},
};
chrome.declarativeNetRequest.updateSessionRules(
{ removeRuleIds: [dnrRequestId], addRules: [rule] },
callback
["blocking", "responseHeaders"]
);
}
function unsetStickyReferrer(dnrRequestId) {
if (dnrRequestId) {
chrome.declarativeNetRequest.updateSessionRules({
removeRuleIds: [dnrRequestId],
});
function onBeforeSendHeaders(details) {
if (details.frameId !== frameId) {
return undefined;
}
var headers = details.requestHeaders;
var refererHeader = getHeaderFromHeaders(headers, "referer");
if (!refererHeader) {
refererHeader = { name: "Referer" };
headers.push(refererHeader);
} else if (
refererHeader.value &&
refererHeader.value.lastIndexOf("chrome-extension:", 0) !== 0
) {
// Sanity check. If the referer is set, and the value is not the URL of
// this extension, then the request was not initiated by this extension.
return undefined;
}
refererHeader.value = referer;
return { requestHeaders: headers };
}
}
function exposeOnHeadersReceived(details) {
if (details.frameId !== frameId) {
return undefined;
}
var headers = details.responseHeaders;
var aceh = getHeaderFromHeaders(headers, "access-control-expose-headers");
// List of headers that PDF.js uses in src/display/network_utils.js
var acehValue =
"accept-ranges,content-encoding,content-length,content-disposition";
if (aceh) {
aceh.value += "," + acehValue;
} else {
aceh = { name: "Access-Control-Expose-Headers", value: acehValue };
headers.push(aceh);
}
return { responseHeaders: headers };
}
});

View File

@ -1,5 +1,6 @@
/*
Copyright 2024 Mozilla Foundation
<!doctype html>
<!--
Copyright 2015 Mozilla Foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -12,15 +13,5 @@ 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.
*/
"use strict";
importScripts(
"options/migration.js",
"preserve-referer.js",
"pdfHandler.js",
"extension-router.js",
"suppress-update.js",
"telemetry.js"
);
-->
<script src="restoretab.js"></script>

View File

@ -0,0 +1,31 @@
/*
Copyright 2015 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.
*/
/**
* This is part of the work-around for crbug.com/511670.
* - chromecom.js sets the URL and history state upon unload.
* - extension-router.js retrieves the saved state and opens restoretab.html
* - restoretab.html (this script) restores the URL and history state.
*/
"use strict";
var url = decodeURIComponent(location.search.slice(1));
var historyState = decodeURIComponent(location.hash.slice(1));
historyState = historyState === "undefined" ? null : JSON.parse(historyState);
history.replaceState(historyState, null, url);
location.reload();

View File

@ -20,10 +20,7 @@ limitations under the License.
// viewer is not displaying any PDF files. Otherwise the tabs would close, which
// is quite disruptive (crbug.com/511670).
chrome.runtime.onUpdateAvailable.addListener(function () {
chrome.tabs.query({ url: chrome.runtime.getURL("*") }, tabs => {
if (tabs?.length) {
return;
}
if (chrome.extension.getViews({ type: "tab" }).length === 0) {
chrome.runtime.reload();
});
}
});

View File

@ -42,35 +42,8 @@ limitations under the License.
return;
}
// The localStorage API is unavailable in service workers. We store data in
// chrome.storage.local and use this "localStorage" object to enable
// synchronous access in the logic.
const localStorage = {
telemetryLastTime: 0,
telemetryDeduplicationId: "",
telemetryLastVersion: "",
};
chrome.alarms.onAlarm.addListener(alarm => {
if (alarm.name === "maybeSendPing") {
maybeSendPing();
}
});
chrome.storage.session.get({ didPingCheck: false }, async items => {
if (items?.didPingCheck) {
return;
}
maybeSendPing();
await chrome.alarms.clear("maybeSendPing");
await chrome.alarms.create("maybeSendPing", { periodInMinutes: 60 });
chrome.storage.session.set({ didPingCheck: true });
});
function updateLocalStorage(key, value) {
localStorage[key] = value;
// Note: We mirror the data in localStorage because the following is async.
chrome.storage.local.set({ [key]: value });
}
maybeSendPing();
setInterval(maybeSendPing, 36e5);
function maybeSendPing() {
getLoggingPref(function (didOptOut) {
@ -88,20 +61,12 @@ limitations under the License.
// send more pings.
return;
}
doSendPing();
});
}
function doSendPing() {
chrome.storage.local.get(localStorage, items => {
Object.assign(localStorage, items);
var lastTime = parseInt(localStorage.telemetryLastTime) || 0;
var wasUpdated = didUpdateSinceLastCheck();
if (!wasUpdated && Date.now() - lastTime < MINIMUM_TIME_BETWEEN_PING) {
return;
}
updateLocalStorage("telemetryLastTime", Date.now());
localStorage.telemetryLastTime = Date.now();
var deduplication_id = getDeduplicationId(wasUpdated);
var extension_version = chrome.runtime.getManifest().version;
@ -139,7 +104,7 @@ limitations under the License.
for (const c of buf) {
id += (c >>> 4).toString(16) + (c & 0xf).toString(16);
}
updateLocalStorage("telemetryDeduplicationId", id);
localStorage.telemetryDeduplicationId = id;
}
return id;
}
@ -154,7 +119,7 @@ limitations under the License.
if (!chromeVersion || localStorage.telemetryLastVersion === chromeVersion) {
return false;
}
updateLocalStorage("telemetryLastVersion", chromeVersion);
localStorage.telemetryLastVersion = chromeVersion;
return true;
}

View File

@ -0,0 +1,22 @@
{
// Note: The root .eslintrc file will define the base rules,
// but mozilla/recommended will override them for the rules it sets. Finally,
// the rules in this file will take precedence.
"extends": [
"plugin:mozilla/recommended",
],
"plugins": [
"mozilla"
],
"rules": {
// Other rules mozilla/recommended hasn't enabled yet.
"no-shadow": "error",
"arrow-body-style": ["error", "as-needed"],
"arrow-parens": ["error", "always"],
"constructor-super": "error",
"no-confusing-arrow": "error",
"no-useless-constructor": "error",
},
}

View File

@ -1,4 +1,4 @@
/* Copyright 2024 Mozilla Foundation
/* Copyright 2018 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -13,16 +13,6 @@
* limitations under the License.
*/
import { InkDrawOutline } from "./inkdraw.js";
class ContourDrawOutline extends InkDrawOutline {
toSVGPath() {
let path = super.toSVGPath();
if (!path.endsWith("Z")) {
path += "Z";
}
return path;
}
}
export { ContourDrawOutline };
export const PdfJsDefaultPreferences = Object.freeze(
PDFJSDev.eval("DEFAULT_PREFERENCES")
);

9
external/.eslintrc vendored Normal file
View File

@ -0,0 +1,9 @@
{
"extends": [
../.eslintrc
],
"env": {
"node": true,
},
}

View File

@ -1,7 +1,10 @@
import { types as t, transformSync } from "@babel/core";
import fs from "fs";
import { join as joinPaths } from "path";
import vm from "vm";
const PDFJS_PREPROCESSOR_NAME = "PDFJSDev";
const ROOT_PREFIX = "$ROOT/";
function isPDFJSPreprocessor(obj) {
return obj.type === "Identifier" && obj.name === PDFJS_PREPROCESSOR_NAME;
@ -37,6 +40,18 @@ function handlePreprocessorAction(ctx, actionName, args, path) {
return result;
}
break;
case "json":
if (!t.isStringLiteral(arg)) {
throw new Error("Path to JSON is not provided");
}
let jsonPath = arg.value;
if (jsonPath.startsWith(ROOT_PREFIX)) {
jsonPath = joinPaths(
ctx.rootPath,
jsonPath.substring(ROOT_PREFIX.length)
);
}
return JSON.parse(fs.readFileSync(jsonPath, "utf8"));
}
throw new Error("Unsupported action");
} catch (e) {
@ -47,25 +62,6 @@ function handlePreprocessorAction(ctx, actionName, args, path) {
}
function babelPluginPDFJSPreprocessor(babel, ctx) {
function removeUnusedFunctions(path) {
let removed;
do {
removed = false;
path.scope.crawl();
for (const name in path.scope.bindings) {
const binding = path.scope.bindings[name];
if (!binding.referenced) {
const { path: bindingPath } = binding;
if (bindingPath.isFunctionDeclaration()) {
bindingPath.remove();
removed = true;
}
}
}
// If we removed some functions, there might be new unused ones
} while (removed);
}
return {
name: "babel-plugin-pdfjs-preprocessor",
manipulateOptions({ parserOpts }) {
@ -172,27 +168,23 @@ function babelPluginPDFJSPreprocessor(babel, ctx) {
path.replaceWith(t.inherits(t.valueToNode(result), path.node));
}
if (t.isIdentifier(node.callee, { name: "__raw_import__" })) {
if (t.isIdentifier(node.callee, { name: "__non_webpack_import__" })) {
if (node.arguments.length !== 1) {
throw new Error("Invalid `__raw_import__` usage.");
throw new Error("Invalid `__non_webpack_import__` usage.");
}
// Replace it with a standard `import`-call and attempt to ensure that
// various bundlers will leave it alone; this *must* include Webpack.
// Replace it with a standard `import`-call and
// ensure that Webpack will leave it alone.
const source = node.arguments[0];
source.leadingComments = [
{
type: "CommentBlock",
value: "webpackIgnore: true",
},
{
type: "CommentBlock",
value: "@vite-ignore",
},
];
path.replaceWith(t.importExpression(source));
}
},
"BlockStatement|StaticBlock": {
BlockStatement: {
// Visit node in post-order so that recursive flattening
// of blocks works correctly.
exit(path) {
@ -223,10 +215,6 @@ function babelPluginPDFJSPreprocessor(babel, ctx) {
}
subExpressionIndex++;
}
if (node.type === "StaticBlock" && node.body.length === 0) {
path.remove();
}
},
},
Function: {
@ -244,8 +232,6 @@ function babelPluginPDFJSPreprocessor(babel, ctx) {
// Function body ends with return without arg -- removing it.
body.pop();
}
removeUnusedFunctions(path);
},
},
ClassMethod: {
@ -268,26 +254,6 @@ function babelPluginPDFJSPreprocessor(babel, ctx) {
}
},
},
Program: {
exit(path) {
if (path.node.sourceType === "module") {
removeUnusedFunctions(path);
}
},
},
MemberExpression(path) {
// The Emscripten Compiler (emcc) generates code that allows the caller
// to provide the Wasm module (thorugh Module.instantiateWasm), with
// a fallback in case .instantiateWasm is not provided.
// We always define instantiateWasm, so we can hard-code the check
// and let our dead code elimination logic remove the unused fallback.
if (
path.parentPath.isIfStatement({ test: path.node }) &&
path.matchesPattern("Module.instantiateWasm")
) {
path.replaceWith(t.booleanLiteral(true));
}
},
},
};
}

View File

@ -151,7 +151,7 @@ function preprocess(inFilename, outFilename, defines) {
let state = STATE_NONE;
const stack = [];
const control =
/^(?:\/\/|\s*\/\*|\s*<!--)\s*#(if|elif|else|endif|expand|include|error)\b(?:\s+(.*?)(?:\*\/|-->)?$)?/;
/^(?:\/\/|\s*\/\*|<!--)\s*#(if|elif|else|endif|expand|include|error)\b(?:\s+(.*?)(?:\*\/|-->)?$)?/;
while ((line = readLine()) !== null) {
++lineNumber;
@ -213,7 +213,7 @@ function preprocess(inFilename, outFilename, defines) {
) {
writeLine(
line
.replaceAll(/^\/\/|^\s*<!--/g, " ")
.replaceAll(/^\/\/|^<!--/g, " ")
.replaceAll(/(^\s*)\/\*/g, "$1 ")
.replaceAll(/\*\/$|-->$/g, "")
);

View File

@ -8,4 +8,3 @@ function test() {
}
"4";
}
test();

View File

@ -17,4 +17,3 @@ function test() {
"4";
}
}
test();

View File

@ -2,12 +2,10 @@ function f1() {
"1";
"2";
}
f1();
function f2() {
"1";
"2";
}
f2();
function f3() {
if ("1") {
"1";
@ -17,4 +15,3 @@ function f3() {
"4";
}
}
f3();

View File

@ -6,7 +6,6 @@ function f1() {
"2";
/* tail */
}
f1();
function f2() {
// head
@ -15,7 +14,6 @@ function f2() {
"2";
// tail
}
f2();
function f3() {
if ("1") { // begin block
@ -26,4 +24,3 @@ function f3() {
"4";
}
}
f3();

View File

@ -1,18 +1,14 @@
function f1() {}
f1();
function f2() {
return 1;
}
f2();
function f3() {
var i = 0;
throw "test";
}
f3();
function f4() {
var i = 0;
}
f4();
var obj = {
method1() {},
method2() {}

View File

@ -2,20 +2,17 @@ function f1() {
return;
var i = 0;
}
f1();
function f2() {
return 1;
var i = 0;
}
f2();
function f3() {
var i = 0;
throw "test";
var j = 0;
}
f3();
function f4() {
var i = 0;
@ -25,7 +22,6 @@ function f4() {
throw "test";
var j = 0;
}
f4();
var obj = {
method1() { return; var i = 0; },

View File

@ -10,6 +10,9 @@ var g = {
},
j: 2
};
var h = {
test: "test"
};
var i = '0';
var j = {
i: 1

View File

@ -5,6 +5,7 @@ var d = PDFJSDev.test('FALSE');
var e = PDFJSDev.eval('TRUE');
var f = PDFJSDev.eval('TEXT');
var g = PDFJSDev.eval('OBJ');
var h = PDFJSDev.json('$ROOT/external/builder/fixtures_babel/evals.json');
var i = typeof PDFJSDev === 'undefined' ? PDFJSDev.eval('FALSE') : '0';
var j = typeof PDFJSDev !== 'undefined' ? PDFJSDev.eval('OBJ.obj') : '0';
var k = !PDFJSDev.test('TRUE');

View File

@ -0,0 +1 @@
{ "test": "test" }

View File

@ -16,4 +16,3 @@ if ('1') {
function f1() {
"1";
}
f1();

View File

@ -32,4 +32,3 @@ function f1() {
"2";
}
}
f1();

View File

@ -1,7 +1,4 @@
import { Test } from "import-name";
import { Test2 } from './non-alias';
export { Test3 } from "import-name";
await import(
/*webpackIgnore: true*/
/*@vite-ignore*/
"./non-alias");
await import( /*webpackIgnore: true*/"./non-alias");

View File

@ -1,4 +1,4 @@
import { Test } from 'import-alias';
import { Test2 } from './non-alias';
export { Test3 } from 'import-alias';
await __raw_import__("./non-alias");
await __non_webpack_import__("./non-alias");

View File

@ -1,8 +0,0 @@
class A {
static {
foo();
}
static {
var a = 0;
}
}

View File

@ -1,20 +0,0 @@
class A {
static {}
static {
{ foo() }
}
static {
{;}
}
static {
if (PDFJSDev.test('TRUE')) {
var a = 0;
}
}
static {
if (PDFJSDev.test('FALSE')) {
var a = 1;
}
}
}

View File

@ -1,5 +0,0 @@
function usedByUsed() {}
function used() {
usedByUsed();
}
used();

View File

@ -1,14 +0,0 @@
function usedByUsed() {}
function usedByUnused() {}
function usedByRemovedCode() {}
function used() {
usedByUsed();
return;
usedByRemovedCode();
}
function unused() {
usedByUnused();
}
used();

View File

@ -1,8 +1,9 @@
import * as builder from "./builder.mjs";
import { fileURLToPath } from "url";
import fs from "fs";
import path from "path";
const __dirname = import.meta.dirname;
const __dirname = path.dirname(fileURLToPath(import.meta.url));
let errors = 0;

View File

@ -1,8 +1,9 @@
import { fileURLToPath } from "url";
import fs from "fs";
import path from "path";
import { preprocessPDFJSCode } from "./babel-plugin-pdfjs-preprocessor.mjs";
const __dirname = import.meta.dirname;
const __dirname = path.dirname(fileURLToPath(import.meta.url));
let errors = 0;

Binary file not shown.

116
external/iccs/LICENSE vendored
View File

@ -1,116 +0,0 @@
CC0 1.0 Universal
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator and
subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later
claims of infringement build upon, modify, incorporate in other works, reuse
and redistribute as freely as possible in any form whatsoever and for any
purposes, including without limitation commercial purposes. These owners may
contribute to the Commons to promote the ideal of a free culture and the
further production of creative, cultural and scientific works, or to gain
reputation or greater distribution for their Work in part through the use and
efforts of others.
For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with a
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
and publicly distribute the Work under its terms, with knowledge of his or her
Copyright and Related Rights in the Work and the meaning and intended legal
effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not limited
to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate,
and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or likeness
depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data in
a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation thereof,
including any amended or successor version of such directive); and
vii. other similar, equivalent or corresponding rights throughout the world
based on applicable law or treaty, and any national implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time
extensions), (iii) in any current or future medium and for any number of
copies, and (iv) for any purpose whatsoever, including without limitation
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
the Waiver for the benefit of each member of the public at large and to the
detriment of Affirmer's heirs and successors, fully intending that such Waiver
shall not be subject to revocation, rescission, cancellation, termination, or
any other legal or equitable action to disrupt the quiet enjoyment of the Work
by the public as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
is so judged Affirmer hereby grants to each affected person a royalty-free,
non transferable, non sublicensable, non exclusive, irrevocable and
unconditional license to exercise Affirmer's Copyright and Related Rights in
the Work (i) in all territories worldwide, (ii) for the maximum duration
provided by applicable law or treaty (including future time extensions), (iii)
in any current or future medium and for any number of copies, and (iv) for any
purpose whatsoever, including without limitation commercial, advertising or
promotional purposes (the "License"). The License shall be deemed effective as
of the date CC0 was applied by Affirmer to the Work. Should any part of the
License for any reason be judged legally invalid or ineffective under
applicable law, such partial invalidity or ineffectiveness shall not
invalidate the remainder of the License, and in such case Affirmer hereby
affirms that he or she will not (i) exercise any of his or her remaining
Copyright and Related Rights in the Work or (ii) assert any associated claims
and causes of action with respect to the Work, in either case contrary to
Affirmer's express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or warranties
of any kind concerning the Work, express, implied, statutory or otherwise,
including without limitation warranties of title, merchantability, fitness
for a particular purpose, non infringement, or the absence of latent or
other defects, accuracy, or the present or absence of errors, whether or not
discoverable, all to the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without limitation
any person's Copyright and Related Rights in the Work. Further, Affirmer
disclaims responsibility for obtaining any necessary consents, permissions
or other rights required for any use of the Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to this
CC0 or use of the Work.
For more information, please see
http://creativecommons.org/publicdomain/zero/1.0/

View File

@ -1,10 +0,0 @@
## ICC
The file CGATS001Compat-v2-micro.icc used to convert colors from CMYK to RGB comes from:
https://github.com/saucecontrol/Compact-ICC-Profiles
at revision bdd84663061bc4ae95ca70decff54f581e27f702.
## Licensing
[CGATS001Compat-v2-micro.icc](https://github.com/saucecontrol/Compact-ICC-Profiles/blob/master/profiles/CGATS001Compat-v2-micro.icc) is under [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/).

View File

@ -31,7 +31,7 @@ async function downloadLanguageCodes() {
console.log("Downloading language codes...\n");
const ALL_LOCALES =
"https://raw.githubusercontent.com/mozilla-firefox/firefox/main/browser/locales/all-locales";
"https://hg.mozilla.org/mozilla-central/raw-file/tip/browser/locales/all-locales";
const response = await fetch(ALL_LOCALES);
if (!response.ok) {
@ -52,9 +52,8 @@ async function downloadLanguageFiles(root, langCode) {
// Constants for constructing the URLs. Translations are taken from the
// Nightly channel as those are the most recent ones.
const MOZ_CENTRAL_ROOT =
"https://raw.githubusercontent.com/mozilla-l10n/firefox-l10n/main/";
const MOZ_CENTRAL_PDFJS_DIR = "/toolkit/toolkit/pdfviewer/";
const MOZ_CENTRAL_ROOT = "https://hg.mozilla.org/l10n-central/";
const MOZ_CENTRAL_PDFJS_DIR = "/raw-file/default/toolkit/toolkit/pdfviewer/";
// Defines which files to download for each language.
const files = ["viewer.ftl"];

View File

@ -1,39 +0,0 @@
/*
* The copyright in this software is being made available under the 2-clauses
* BSD License, included below. This software may be subject to other third
* party and contributor rights, including patent rights, and no such rights
* are granted under this license.
*
* Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium
* Copyright (c) 2002-2014, Professor Benoit Macq
* Copyright (c) 2003-2014, Antonin Descampe
* Copyright (c) 2003-2009, Francois-Olivier Devaux
* Copyright (c) 2005, Herve Drolon, FreeImage Team
* Copyright (c) 2002-2003, Yannick Verschueren
* Copyright (c) 2001-2003, David Janssens
* Copyright (c) 2011-2012, Centre National d'Etudes Spatiales (CNES), France
* Copyright (c) 2012, CS Systemes d'Information, France
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS'
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/

View File

@ -1,22 +0,0 @@
Copyright (c) 2024, Mozilla Foundation
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -1,22 +0,0 @@
Copyright (c) 2025, Mozilla Foundation
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,21 +0,0 @@
qcms
Copyright (C) 2009-2024 Mozilla Corporation
Copyright (C) 1998-2007 Marti Maria
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,12 +0,0 @@
## Build
In order to generate the files `qcms.js` and `qcms_bg.wasm`:
* git clone https://github.com/mozilla/pdf.js.qcms/
* the build requires to have a [Docker](https://www.docker.com/) setup and then:
* `node build.js -C` to build the Docker image
* `node build.js -co /pdf.js/external/qcms/` to compile the decoder
## Licensing
[qcms](https://github.com/FirefoxGraphics/qcms) is under [MIT](https://github.com/FirefoxGraphics/qcms/blob/main/COPYING)
and [pdf.js.qcms](https://github.com/mozilla/pdf.js.qcms/) is released under [MIT](https://github.com/mozilla/pdf.js.qcms/blob/main/LICENSE) license so `qcms.js` and `qcms_bg.wasm` are released under [MIT](https://github.com/mozilla/pdf.js.qcms/blob/main/LICENSE) license too.

259
external/qcms/qcms.js vendored
View File

@ -1,259 +0,0 @@
/* THIS FILE IS GENERATED - DO NOT EDIT */
import { copy_result, copy_rgb, make_cssRGB } from './qcms_utils.js';
let wasm;
const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
let cachedUint8ArrayMemory0 = null;
function getUint8ArrayMemory0() {
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
}
return cachedUint8ArrayMemory0;
}
function getStringFromWasm0(ptr, len) {
ptr = ptr >>> 0;
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
}
let WASM_VECTOR_LEN = 0;
function passArray8ToWasm0(arg, malloc) {
const ptr = malloc(arg.length * 1, 1) >>> 0;
getUint8ArrayMemory0().set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length;
return ptr;
}
/**
* # Safety
*
* This function is called directly from JavaScript.
* @param {number} transformer
* @param {Uint8Array} src
*/
export function qcms_convert_array(transformer, src) {
const ptr0 = passArray8ToWasm0(src, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
wasm.qcms_convert_array(transformer, ptr0, len0);
}
/**
* # Safety
*
* This function is called directly from JavaScript.
* @param {number} transformer
* @param {number} src
* @param {boolean} css
*/
export function qcms_convert_one(transformer, src, css) {
wasm.qcms_convert_one(transformer, src, css);
}
/**
* # Safety
*
* This function is called directly from JavaScript.
* @param {number} transformer
* @param {number} src1
* @param {number} src2
* @param {number} src3
* @param {boolean} css
*/
export function qcms_convert_three(transformer, src1, src2, src3, css) {
wasm.qcms_convert_three(transformer, src1, src2, src3, css);
}
/**
* # Safety
*
* This function is called directly from JavaScript.
* @param {number} transformer
* @param {number} src1
* @param {number} src2
* @param {number} src3
* @param {number} src4
* @param {boolean} css
*/
export function qcms_convert_four(transformer, src1, src2, src3, src4, css) {
wasm.qcms_convert_four(transformer, src1, src2, src3, src4, css);
}
/**
* # Safety
*
* This function is called directly from JavaScript.
* @param {Uint8Array} mem
* @param {DataType} in_type
* @param {Intent} intent
* @returns {number}
*/
export function qcms_transformer_from_memory(mem, in_type, intent) {
const ptr0 = passArray8ToWasm0(mem, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.qcms_transformer_from_memory(ptr0, len0, in_type, intent);
return ret >>> 0;
}
/**
* # Safety
*
* This function is called directly from JavaScript.
* @param {number} transformer
*/
export function qcms_drop_transformer(transformer) {
wasm.qcms_drop_transformer(transformer);
}
/**
* @enum {0 | 1 | 2 | 3 | 4 | 5}
*/
export const DataType = Object.freeze({
RGB8: 0, "0": "RGB8",
RGBA8: 1, "1": "RGBA8",
BGRA8: 2, "2": "BGRA8",
Gray8: 3, "3": "Gray8",
GrayA8: 4, "4": "GrayA8",
CMYK: 5, "5": "CMYK",
});
/**
* @enum {0 | 1 | 2 | 3}
*/
export const Intent = Object.freeze({
Perceptual: 0, "0": "Perceptual",
RelativeColorimetric: 1, "1": "RelativeColorimetric",
Saturation: 2, "2": "Saturation",
AbsoluteColorimetric: 3, "3": "AbsoluteColorimetric",
});
async function __wbg_load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else {
throw e;
}
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
}
function __wbg_get_imports() {
const imports = {};
imports.wbg = {};
imports.wbg.__wbg_copyresult_b08ee7d273f295dd = function(arg0, arg1) {
copy_result(arg0 >>> 0, arg1 >>> 0);
};
imports.wbg.__wbg_copyrgb_d60ce17bb05d9b67 = function(arg0) {
copy_rgb(arg0 >>> 0);
};
imports.wbg.__wbg_makecssRGB_893bf0cd9fdb302d = function(arg0) {
make_cssRGB(arg0 >>> 0);
};
imports.wbg.__wbindgen_init_externref_table = function() {
const table = wasm.__wbindgen_export_0;
const offset = table.grow(4);
table.set(0, undefined);
table.set(offset + 0, undefined);
table.set(offset + 1, null);
table.set(offset + 2, true);
table.set(offset + 3, false);
;
};
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
};
return imports;
}
function __wbg_init_memory(imports, memory) {
}
function __wbg_finalize_init(instance, module) {
wasm = instance.exports;
__wbg_init.__wbindgen_wasm_module = module;
cachedUint8ArrayMemory0 = null;
wasm.__wbindgen_start();
return wasm;
}
function initSync(module) {
if (wasm !== undefined) return wasm;
if (typeof module !== 'undefined') {
if (Object.getPrototypeOf(module) === Object.prototype) {
({module} = module)
} else {
console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
}
}
const imports = __wbg_get_imports();
__wbg_init_memory(imports);
if (!(module instanceof WebAssembly.Module)) {
module = new WebAssembly.Module(module);
}
const instance = new WebAssembly.Instance(module, imports);
return __wbg_finalize_init(instance, module);
}
async function __wbg_init(module_or_path) {
if (wasm !== undefined) return wasm;
if (typeof module_or_path !== 'undefined') {
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
({module_or_path} = module_or_path)
} else {
console.warn('using deprecated parameters for the initialization function; pass a single object instead')
}
}
const imports = __wbg_get_imports();
if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
module_or_path = fetch(module_or_path);
}
__wbg_init_memory(imports);
const { instance, module } = await __wbg_load(await module_or_path, imports);
return __wbg_finalize_init(instance, module);
}
export { initSync };
export default __wbg_init;

Binary file not shown.

View File

@ -1,84 +0,0 @@
/* Copyright 2025 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.
*/
class QCMS {
static #memoryArray = null;
static _memory = null;
static _mustAddAlpha = false;
static _destBuffer = null;
static _destOffset = 0;
static _destLength = 0;
static _cssColor = "";
static _makeHexColor = null;
static get _memoryArray() {
const array = this.#memoryArray;
if (array?.byteLength) {
return array;
}
return (this.#memoryArray = new Uint8Array(this._memory.buffer));
}
}
function copy_result(ptr, len) {
// This function is called from the wasm module (it's an external
// "C" function). Its goal is to copy the result from the wasm memory
// to the destination buffer without any intermediate copies.
const { _mustAddAlpha, _destBuffer, _destOffset, _destLength, _memoryArray } =
QCMS;
if (len === _destLength) {
_destBuffer.set(_memoryArray.subarray(ptr, ptr + len), _destOffset);
return;
}
if (_mustAddAlpha) {
for (let i = ptr, ii = ptr + len, j = _destOffset; i < ii; i += 3, j += 4) {
_destBuffer[j] = _memoryArray[i];
_destBuffer[j + 1] = _memoryArray[i + 1];
_destBuffer[j + 2] = _memoryArray[i + 2];
_destBuffer[j + 3] = 255;
}
} else {
for (let i = ptr, ii = ptr + len, j = _destOffset; i < ii; i += 3, j += 4) {
_destBuffer[j] = _memoryArray[i];
_destBuffer[j + 1] = _memoryArray[i + 1];
_destBuffer[j + 2] = _memoryArray[i + 2];
}
}
}
function copy_rgb(ptr) {
const { _destBuffer, _destOffset, _memoryArray } = QCMS;
_destBuffer[_destOffset] = _memoryArray[ptr];
_destBuffer[_destOffset + 1] = _memoryArray[ptr + 1];
_destBuffer[_destOffset + 2] = _memoryArray[ptr + 2];
}
function make_cssRGB(ptr) {
const { _memoryArray } = QCMS;
QCMS._cssColor = QCMS._makeHexColor(
_memoryArray[ptr],
_memoryArray[ptr + 1],
_memoryArray[ptr + 2]
);
}
export { copy_result, copy_rgb, make_cssRGB, QCMS };

View File

@ -12,6 +12,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-env node */
import {
babelPluginPDFJSPreprocessor,
@ -21,6 +22,7 @@ import { exec, execSync, spawn, spawnSync } from "child_process";
import autoprefixer from "autoprefixer";
import babel from "@babel/core";
import crypto from "crypto";
import { fileURLToPath } from "url";
import fs from "fs";
import gulp from "gulp";
import hljs from "highlight.js";
@ -30,26 +32,28 @@ import Metalsmith from "metalsmith";
import ordered from "ordered-read-streams";
import path from "path";
import postcss from "gulp-postcss";
import postcssDarkThemeClass from "postcss-dark-theme-class";
import postcssDirPseudoClass from "postcss-dir-pseudo-class";
import postcssDiscardComments from "postcss-discard-comments";
import postcssLightDarkFunction from "@csstools/postcss-light-dark-function";
import postcssNesting from "postcss-nesting";
import { preprocess } from "./external/builder/builder.mjs";
import relative from "metalsmith-html-relative";
import rename from "gulp-rename";
import replace from "gulp-replace";
import stream from "stream";
import streamqueue from "streamqueue";
import TerserPlugin from "terser-webpack-plugin";
import Vinyl from "vinyl";
import webpack2 from "webpack";
import webpackStream from "webpack-stream";
import zip from "gulp-zip";
const __dirname = import.meta.dirname;
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const BUILD_DIR = "build/";
const L10N_DIR = "l10n/";
const TEST_DIR = "test/";
const EXTENSION_SRC_DIR = "extensions/";
const BASELINE_DIR = BUILD_DIR + "baseline/";
const MOZCENTRAL_BASELINE_DIR = BUILD_DIR + "mozcentral.baseline/";
@ -74,15 +78,17 @@ const COMMON_WEB_FILES = [
];
const MOZCENTRAL_DIFF_FILE = "mozcentral.diff";
const DIST_REPO_URL = "https://github.com/mozilla/pdfjs-dist";
const CONFIG_FILE = "pdfjs.config";
const config = JSON.parse(fs.readFileSync(CONFIG_FILE).toString());
const ENV_TARGETS = [
"last 2 versions",
"Chrome >= 110",
"Chrome >= 98",
"Firefox ESR",
"Safari >= 16.4",
"Node >= 20",
"Node >= 18",
"> 1%",
"not IE > 0",
"not dead",
@ -96,7 +102,7 @@ const AUTOPREFIXER_CONFIG = {
const BABEL_TARGETS = ENV_TARGETS.join(", ");
const BABEL_PRESET_ENV_OPTS = Object.freeze({
corejs: "3.46.0",
corejs: "3.37.1",
exclude: ["web.structured-clone"],
shippedProposals: true,
useBuiltIns: "usage",
@ -189,9 +195,6 @@ function createWebpackAlias(defines) {
"fluent-dom": "node_modules/@fluent/dom/esm/index.js",
};
const libraryAlias = {
"display-cmap_reader_factory": "src/display/stubs.js",
"display-standard_fontdata_factory": "src/display/stubs.js",
"display-wasm_factory": "src/display/stubs.js",
"display-fetch_stream": "src/display/stubs.js",
"display-network": "src/display/stubs.js",
"display-node_stream": "src/display/stubs.js",
@ -202,7 +205,6 @@ function createWebpackAlias(defines) {
"web-annotation_editor_params": "web/annotation_editor_params.js",
"web-download_manager": "",
"web-external_services": "",
"web-new_alt_text_manager": "web/new_alt_text_manager.js",
"web-null_l10n": "",
"web-pdf_attachment_viewer": "web/pdf_attachment_viewer.js",
"web-pdf_cursor_tools": "web/pdf_cursor_tools.js",
@ -216,16 +218,10 @@ function createWebpackAlias(defines) {
"web-preferences": "",
"web-print_service": "",
"web-secondary_toolbar": "web/secondary_toolbar.js",
"web-signature_manager": "web/signature_manager.js",
"web-toolbar": "web/toolbar.js",
};
if (defines.CHROME) {
libraryAlias["display-cmap_reader_factory"] =
"src/display/cmap_reader_factory.js";
libraryAlias["display-standard_fontdata_factory"] =
"src/display/standard_fontdata_factory.js";
libraryAlias["display-wasm_factory"] = "src/display/wasm_factory.js";
libraryAlias["display-fetch_stream"] = "src/display/fetch_stream.js";
libraryAlias["display-network"] = "src/display/network.js";
@ -238,11 +234,6 @@ function createWebpackAlias(defines) {
// Aliases defined here must also be replicated in the paths section of
// the tsconfig.json file for the type generation to work.
// In the tsconfig.json files, the .js extension must be omitted.
libraryAlias["display-cmap_reader_factory"] =
"src/display/cmap_reader_factory.js";
libraryAlias["display-standard_fontdata_factory"] =
"src/display/standard_fontdata_factory.js";
libraryAlias["display-wasm_factory"] = "src/display/wasm_factory.js";
libraryAlias["display-fetch_stream"] = "src/display/fetch_stream.js";
libraryAlias["display-network"] = "src/display/network.js";
libraryAlias["display-node_stream"] = "src/display/node_stream.js";
@ -294,6 +285,9 @@ function createWebpackConfig(
BUNDLE_VERSION: versionInfo.version,
BUNDLE_BUILD: versionInfo.commit,
TESTING: defines.TESTING ?? process.env.TESTING === "true",
BROWSER_PREFERENCES: defaultPreferencesDir
? getBrowserPreferences(defaultPreferencesDir)
: {},
DEFAULT_PREFERENCES: defaultPreferencesDir
? getDefaultPreferences(defaultPreferencesDir)
: {},
@ -302,12 +296,6 @@ function createWebpackConfig(
const licenseHeaderLibre = fs
.readFileSync("./src/license_header_libre.js")
.toString();
const versionInfoHeader = [
"/**",
` * pdfjsVersion = ${versionInfo.version}`,
` * pdfjsBuild = ${versionInfo.commit}`,
" */",
].join("\n");
const enableSourceMaps =
!bundleDefines.MOZCENTRAL &&
!bundleDefines.CHROME &&
@ -341,39 +329,9 @@ function createWebpackConfig(
const plugins = [];
if (!disableLicenseHeader) {
plugins.push(
new webpack2.BannerPlugin({
banner: licenseHeaderLibre + "\n" + versionInfoHeader,
raw: true,
})
new webpack2.BannerPlugin({ banner: licenseHeaderLibre, raw: true })
);
}
plugins.push({
/** @param {import('webpack').Compiler} compiler */
apply(compiler) {
const errors = [];
compiler.hooks.afterCompile.tap("VerifyImportMeta", compilation => {
for (const asset of compilation.getAssets()) {
if (asset.name.endsWith(".mjs")) {
const source = asset.source.source();
if (
typeof source === "string" &&
/new URL\([^,)]*,\s*import\.meta\.url/.test(source)
) {
errors.push(
`Output module ${asset.name} uses new URL(..., import.meta.url)`
);
}
}
}
});
compiler.hooks.afterEmit.tap("VerifyImportMeta", compilation => {
// Emit the errors after emitting the files, so that it's possible to
// look at the contents of the invalid bundle.
compilation.errors.push(...errors);
});
},
});
const alias = createWebpackAlias(bundleDefines);
const experiments = isModule ? { outputModule: true } : undefined;
@ -397,8 +355,9 @@ function createWebpackConfig(
// V8 chokes on very long sequences, work around that.
sequences: false,
},
format: {
comments: /@lic|webpackIgnore|@vite-ignore|pdfjsVersion/i,
mangle: {
// Ensure that the `tweakWebpackOutput` function works.
reserved: ["__webpack_exports__"],
},
keep_classnames: true,
keep_fnames: true,
@ -418,15 +377,8 @@ function createWebpackConfig(
},
devtool: enableSourceMaps ? "source-map" : undefined,
module: {
parser: {
javascript: {
importMeta: false,
url: false,
},
},
rules: [
{
test: /\.[mc]?js$/,
loader: "babel-loader",
exclude: babelExcludeRegExp,
options: {
@ -497,6 +449,13 @@ function checkChromePreferencesFile(chromePrefsPath, webPrefs) {
return ret;
}
function tweakWebpackOutput(jsName) {
return replace(
/((?:\s|,)__webpack_exports__)(?:\s?)=(?:\s?)({};)/gm,
(match, p1, p2) => `${p1} = globalThis.${jsName} = ${p2}`
);
}
function createMainBundle(defines) {
const mainFileConfig = createWebpackConfig(defines, {
filename: defines.MINIFIED ? "pdf.min.mjs" : "pdf.mjs",
@ -506,7 +465,8 @@ function createMainBundle(defines) {
});
return gulp
.src("./src/pdf.js", { encoding: false })
.pipe(webpack2Stream(mainFileConfig));
.pipe(webpack2Stream(mainFileConfig))
.pipe(tweakWebpackOutput("pdfjsLib"));
}
function createScriptingBundle(defines, extraOptions = undefined) {
@ -574,7 +534,8 @@ function createSandboxBundle(defines, extraOptions = undefined) {
return gulp
.src("./src/pdf.sandbox.js", { encoding: false })
.pipe(webpack2Stream(sandboxFileConfig));
.pipe(webpack2Stream(sandboxFileConfig))
.pipe(tweakWebpackOutput("pdfjsSandbox"));
}
function createWorkerBundle(defines) {
@ -586,7 +547,8 @@ function createWorkerBundle(defines) {
});
return gulp
.src("./src/pdf.worker.js", { encoding: false })
.pipe(webpack2Stream(workerFileConfig));
.pipe(webpack2Stream(workerFileConfig))
.pipe(tweakWebpackOutput("pdfjsWorker"));
}
function createWebBundle(defines, options) {
@ -634,7 +596,8 @@ function createComponentsBundle(defines) {
});
return gulp
.src("./web/pdf_viewer.component.js", { encoding: false })
.pipe(webpack2Stream(componentsFileConfig));
.pipe(webpack2Stream(componentsFileConfig))
.pipe(tweakWebpackOutput("pdfjsViewer"));
}
function createImageDecodersBundle(defines) {
@ -648,7 +611,8 @@ function createImageDecodersBundle(defines) {
});
return gulp
.src("./src/pdf.image_decoders.js", { encoding: false })
.pipe(webpack2Stream(componentsFileConfig));
.pipe(webpack2Stream(componentsFileConfig))
.pipe(tweakWebpackOutput("pdfjsImageDecoders"));
}
function createCMapBundle() {
@ -658,19 +622,13 @@ function createCMapBundle() {
});
}
function createICCBundle() {
return gulp.src(["external/iccs/*.icc", "external/iccs/LICENSE"], {
base: "external/iccs",
encoding: false,
});
}
function createStandardFontBundle() {
return gulp.src(
[
"external/standard_fonts/*.pfb",
"external/standard_fonts/*.ttf",
"external/standard_fonts/LICENSE_*",
"external/standard_fonts/LICENSE_FOXIT",
"external/standard_fonts/LICENSE_LIBERATION",
],
{
base: "external/standard_fonts",
@ -679,26 +637,6 @@ function createStandardFontBundle() {
);
}
function createWasmBundle() {
return ordered([
gulp.src(
[
"external/openjpeg/*.wasm",
"external/openjpeg/openjpeg_nowasm_fallback.js",
"external/openjpeg/LICENSE_*",
],
{
base: "external/openjpeg",
encoding: false,
}
),
gulp.src(["external/qcms/*.wasm", "external/qcms/LICENSE_*"], {
base: "external/qcms",
encoding: false,
}),
]);
}
function checkFile(filePath) {
try {
const stat = fs.lstatSync(filePath);
@ -731,8 +669,9 @@ function getTempFile(prefix, suffix) {
return filePath;
}
function runTests(testsName, { bot = false, xfaOnly = false } = {}) {
return new Promise((resolve, reject) => {
function createTestSource(testsName, { bot = false, xfaOnly = false } = {}) {
const source = stream.Readable({ objectMode: true });
source._read = function () {
console.log();
console.log("### Running " + testsName + " tests");
@ -744,21 +683,17 @@ function runTests(testsName, { bot = false, xfaOnly = false } = {}) {
if (!bot) {
args.push("--reftest");
} else {
// The browser-tests are too slow in Google Chrome on the bots,
// causing a timeout, hence disabling them for now.
forceNoChrome = true;
const os = process.env.OS;
if (/windows/i.test(os)) {
// The browser-tests are too slow in Google Chrome on the Windows
// bot, causing a timeout, hence disabling them for now.
forceNoChrome = true;
}
}
if (xfaOnly) {
args.push("--xfaOnly");
}
args.push("--manifestFile=" + PDF_TEST);
collectArgs(
{
names: ["-t", "--testfilter"],
hasValue: true,
},
args
);
break;
case "unit":
args.push("--unitTest");
@ -770,8 +705,8 @@ function runTests(testsName, { bot = false, xfaOnly = false } = {}) {
args.push("--integration");
break;
default:
reject(new Error(`Unknown tests name '${testsName}'`));
return;
this.emit("error", new Error("Unknown name: " + testsName));
return null;
}
if (bot) {
args.push("--strictVerify");
@ -779,9 +714,6 @@ function runTests(testsName, { bot = false, xfaOnly = false } = {}) {
if (process.argv.includes("--noChrome") || forceNoChrome) {
args.push("--noChrome");
}
if (process.argv.includes("--noFirefox")) {
args.push("--noFirefox");
}
if (process.argv.includes("--headless")) {
args.push("--headless");
}
@ -789,33 +721,13 @@ function runTests(testsName, { bot = false, xfaOnly = false } = {}) {
const testProcess = startNode(args, { cwd: TEST_DIR, stdio: "inherit" });
testProcess.on("close", function (code) {
if (code !== 0) {
reject(new Error(`Running ${testsName} tests failed.`));
throw new Error(`Running ${testsName} tests failed.`);
}
resolve();
source.push(null);
});
});
}
function collectArgs(options, args) {
if (!Array.isArray(options)) {
options = [options];
}
for (let i = 0, ii = process.argv.length; i < ii; i++) {
const arg = process.argv[i];
const option = options.find(opt => opt.names.includes(arg));
if (!option) {
continue;
}
if (!option.hasValue) {
args.push(arg);
continue;
}
const next = process.argv[i + 1];
if (next && !next.startsWith("-")) {
args.push(arg, next);
i += 1;
}
}
return undefined;
};
return source;
}
function makeRef(done, bot) {
@ -825,28 +737,20 @@ function makeRef(done, bot) {
let forceNoChrome = false;
const args = ["test.mjs", "--masterMode"];
if (bot) {
// The browser-tests are too slow in Google Chrome on the bots,
// causing a timeout, hence disabling them for now.
forceNoChrome = true;
const os = process.env.OS;
if (/windows/i.test(os)) {
// The browser-tests are too slow in Google Chrome on the Windows
// bot, causing a timeout, hence disabling them for now.
forceNoChrome = true;
}
args.push("--noPrompts", "--strictVerify");
}
if (process.argv.includes("--noChrome") || forceNoChrome) {
args.push("--noChrome");
}
if (process.argv.includes("--noFirefox")) {
args.push("--noFirefox");
}
if (process.argv.includes("--headless")) {
args.push("--headless");
}
collectArgs(
{
names: ["-t", "--testfilter"],
hasValue: true,
},
args
);
const testProcess = startNode(args, { cwd: TEST_DIR, stdio: "inherit" });
testProcess.on("close", function (code) {
@ -953,6 +857,13 @@ async function parseDefaultPreferences(dir) {
"./" + DEFAULT_PREFERENCES_DIR + dir + "app_options.mjs"
);
const browserPrefs = AppOptions.getAll(
OptionKind.BROWSER,
/* defaultOnly = */ true
);
if (Object.keys(browserPrefs).length === 0) {
throw new Error("No browser preferences found.");
}
const prefs = AppOptions.getAll(
OptionKind.PREFERENCE,
/* defaultOnly = */ true
@ -961,12 +872,23 @@ async function parseDefaultPreferences(dir) {
throw new Error("No default preferences found.");
}
fs.writeFileSync(
DEFAULT_PREFERENCES_DIR + dir + "browser_preferences.json",
JSON.stringify(browserPrefs)
);
fs.writeFileSync(
DEFAULT_PREFERENCES_DIR + dir + "default_preferences.json",
JSON.stringify(prefs)
);
}
function getBrowserPreferences(dir) {
const str = fs
.readFileSync(DEFAULT_PREFERENCES_DIR + dir + "browser_preferences.json")
.toString();
return JSON.parse(str);
}
function getDefaultPreferences(dir) {
const str = fs
.readFileSync(DEFAULT_PREFERENCES_DIR + dir + "default_preferences.json")
@ -1120,9 +1042,7 @@ function buildGeneric(defines, dir) {
})
.pipe(gulp.dest(dir + "web")),
createCMapBundle().pipe(gulp.dest(dir + "web/cmaps")),
createICCBundle().pipe(gulp.dest(dir + "web/iccs")),
createStandardFontBundle().pipe(gulp.dest(dir + "web/standard_fonts")),
createWasmBundle().pipe(gulp.dest(dir + "web/wasm")),
preprocessHTML("web/viewer.html", defines).pipe(gulp.dest(dir + "web")),
preprocessCSS("web/viewer.css", defines)
@ -1131,7 +1051,7 @@ function buildGeneric(defines, dir) {
postcssDirPseudoClass(),
discardCommentsCSS(),
postcssNesting(),
postcssLightDarkFunction({ preserve: true }),
postcssDarkThemeClass(),
autoprefixer(AUTOPREFIXER_CONFIG),
])
)
@ -1205,10 +1125,8 @@ function buildComponents(defines, dir) {
"web/images/loading-icon.gif",
"web/images/altText_*.svg",
"web/images/editor-toolbar-*.svg",
"web/images/messageBar_*.svg",
"web/images/toolbarButton-{editorHighlight,menuArrow}.svg",
"web/images/cursor-*.svg",
"web/images/comment-*.svg",
];
return ordered([
@ -1222,7 +1140,6 @@ function buildComponents(defines, dir) {
postcssDirPseudoClass(),
discardCommentsCSS(),
postcssNesting(),
postcssLightDarkFunction({ preserve: true }),
autoprefixer(AUTOPREFIXER_CONFIG),
])
)
@ -1361,31 +1278,26 @@ gulp.task(
)
);
function createDefaultPrefsFile() {
const defaultFileName = "PdfJsDefaultPrefs.js",
overrideFileName = "PdfJsOverridePrefs.js";
function preprocessDefaultPreferences(content) {
const licenseHeader = fs.readFileSync("./src/license_header.js").toString();
const MODIFICATION_WARNING =
"// THIS FILE IS GENERATED AUTOMATICALLY, DO NOT EDIT MANUALLY!\n//\n" +
`// Any overrides should be placed in \`${overrideFileName}\`.\n`;
"//\n// THIS FILE IS GENERATED AUTOMATICALLY, DO NOT EDIT MANUALLY!\n//\n";
const prefs = getDefaultPreferences("mozcentral/");
const buf = [];
const bundleDefines = {
...DEFINES,
DEFAULT_PREFERENCES: getDefaultPreferences("mozcentral/"),
};
for (const name in prefs) {
let value = prefs[name];
content = preprocessPDFJSCode(
{
rootPath: __dirname,
defines: bundleDefines,
},
content
);
if (typeof value === "string") {
value = `"${value}"`;
}
buf.push(`pref("pdfjs.${name}", ${value});`);
}
buf.sort();
buf.unshift(licenseHeader, MODIFICATION_WARNING);
buf.push(`\n#include ${overrideFileName}\n`);
return createStringSource(defaultFileName, buf.join("\n"));
return licenseHeader + "\n" + MODIFICATION_WARNING + "\n" + content + "\n";
}
function replaceMozcentralCSS() {
@ -1413,7 +1325,8 @@ gulp.task(
MOZCENTRAL_EXTENSION_DIR = MOZCENTRAL_DIR + "browser/extensions/pdfjs/",
MOZCENTRAL_CONTENT_DIR = MOZCENTRAL_EXTENSION_DIR + "content/",
MOZCENTRAL_L10N_DIR =
MOZCENTRAL_DIR + "browser/locales/en-US/pdfviewer/";
MOZCENTRAL_DIR + "browser/locales/en-US/pdfviewer/",
FIREFOX_CONTENT_DIR = EXTENSION_SRC_DIR + "/firefox/content/";
const MOZCENTRAL_WEB_FILES = [
...COMMON_WEB_FILES,
@ -1451,11 +1364,9 @@ gulp.task(
createCMapBundle().pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "web/cmaps")
),
createICCBundle().pipe(gulp.dest(MOZCENTRAL_CONTENT_DIR + "web/iccs")),
createStandardFontBundle().pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "web/standard_fonts")
),
createWasmBundle().pipe(gulp.dest(MOZCENTRAL_CONTENT_DIR + "web/wasm")),
preprocessHTML("web/viewer.html", defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "web")
@ -1490,7 +1401,12 @@ gulp.task(
gulp
.src("LICENSE", { encoding: false })
.pipe(gulp.dest(MOZCENTRAL_EXTENSION_DIR)),
createDefaultPrefsFile().pipe(gulp.dest(MOZCENTRAL_EXTENSION_DIR)),
gulp
.src(FIREFOX_CONTENT_DIR + "PdfJsDefaultPreferences.sys.mjs", {
encoding: false,
})
.pipe(transform("utf8", preprocessDefaultPreferences))
.pipe(gulp.dest(MOZCENTRAL_CONTENT_DIR)),
]);
}
)
@ -1555,15 +1471,9 @@ gulp.task(
createCMapBundle().pipe(
gulp.dest(CHROME_BUILD_CONTENT_DIR + "web/cmaps")
),
createICCBundle().pipe(
gulp.dest(CHROME_BUILD_CONTENT_DIR + "web/iccs")
),
createStandardFontBundle().pipe(
gulp.dest(CHROME_BUILD_CONTENT_DIR + "web/standard_fonts")
),
createWasmBundle().pipe(
gulp.dest(CHROME_BUILD_CONTENT_DIR + "web/wasm")
),
preprocessHTML("web/viewer.html", defines).pipe(
gulp.dest(CHROME_BUILD_CONTENT_DIR + "web")
@ -1574,7 +1484,7 @@ gulp.task(
postcssDirPseudoClass(),
discardCommentsCSS(),
postcssNesting(),
postcssLightDarkFunction({ preserve: true }),
postcssDarkThemeClass(),
autoprefixer(AUTOPREFIXER_CONFIG),
])
)
@ -1648,9 +1558,6 @@ function buildLibHelper(bundleDefines, inputStream, outputDir) {
defines: bundleDefines,
map: {
"pdfjs-lib": "../pdf.js",
"display-cmap_reader_factory": "./cmap_reader_factory.js",
"display-standard_fontdata_factory": "./standard_fontdata_factory.js",
"display-wasm_factory": "./wasm_factory.js",
"display-fetch_stream": "./fetch_stream.js",
"display-network": "./network.js",
"display-node_stream": "./node_stream.js",
@ -1676,6 +1583,9 @@ function buildLib(defines, dir) {
BUNDLE_VERSION: versionInfo.version,
BUNDLE_BUILD: versionInfo.commit,
TESTING: defines.TESTING ?? process.env.TESTING === "true",
BROWSER_PREFERENCES: getBrowserPreferences(
defines.SKIP_BABEL ? "lib/" : "lib-legacy/"
),
DEFAULT_PREFERENCES: getDefaultPreferences(
defines.SKIP_BABEL ? "lib/" : "lib-legacy/"
),
@ -1696,7 +1606,6 @@ function buildLib(defines, dir) {
}),
gulp.src("test/unit/*.js", { base: ".", encoding: false }),
gulp.src("external/openjpeg/*.js", { base: "openjpeg/", encoding: false }),
gulp.src("external/qcms/*.js", { base: "qcms/", encoding: false }),
]);
return buildLibHelper(bundleDefines, inputStream, dir);
@ -1824,55 +1733,57 @@ function setTestEnv(done) {
gulp.task(
"test",
gulp.series(setTestEnv, "generic", "components", async function runTest() {
await runTests("unit");
await runTests("browser");
await runTests("integration");
gulp.series(setTestEnv, "generic", "components", function runTest() {
return streamqueue(
{ objectMode: true },
createTestSource("unit"),
createTestSource("browser"),
createTestSource("integration")
);
})
);
gulp.task(
"bottest",
gulp.series(setTestEnv, "generic", "components", async function runBotTest() {
await runTests("unit", { bot: true });
await runTests("browser", { bot: true });
await runTests("integration");
gulp.series(setTestEnv, "generic", "components", function runBotTest() {
return streamqueue(
{ objectMode: true },
createTestSource("unit", { bot: true }),
createTestSource("browser", { bot: true }),
createTestSource("integration")
);
})
);
gulp.task(
"xfatest",
gulp.series(setTestEnv, "generic", "components", async function runXfaTest() {
await runTests("unit");
await runTests("browser", { xfaOnly: true });
await runTests("integration");
gulp.series(setTestEnv, "generic", "components", function runXfaTest() {
return streamqueue(
{ objectMode: true },
createTestSource("unit"),
createTestSource("browser", { xfaOnly: true }),
createTestSource("integration")
);
})
);
gulp.task(
"botxfatest",
gulp.series(
setTestEnv,
"generic",
"components",
async function runBotXfaTest() {
await runTests("unit", { bot: true });
await runTests("browser", { bot: true, xfaOnly: true });
await runTests("integration");
}
)
gulp.series(setTestEnv, "generic", "components", function runBotXfaTest() {
return streamqueue(
{ objectMode: true },
createTestSource("unit", { bot: true }),
createTestSource("browser", { bot: true, xfaOnly: true }),
createTestSource("integration")
);
})
);
gulp.task(
"browsertest",
gulp.series(
setTestEnv,
"generic",
"components",
async function runBrowserTest() {
await runTests("browser");
}
)
gulp.series(setTestEnv, "generic", "components", function runBrowserTest() {
return createTestSource("browser");
})
);
gulp.task(
@ -1881,30 +1792,33 @@ gulp.task(
setTestEnv,
"generic",
"components",
async function runBotBrowserTest() {
await runTests("browser", { bot: true });
function runBotBrowserTest() {
return streamqueue(
{ objectMode: true },
createTestSource("browser", { bot: true })
);
}
)
);
gulp.task(
"unittest",
gulp.series(setTestEnv, "generic", async function runUnitTest() {
await runTests("unit");
gulp.series(setTestEnv, "generic", function runUnitTest() {
return createTestSource("unit");
})
);
gulp.task(
"integrationtest",
gulp.series(setTestEnv, "generic", async function runIntegrationTest() {
await runTests("integration");
gulp.series(setTestEnv, "generic", function runIntegrationTest() {
return createTestSource("integration");
})
);
gulp.task(
"fonttest",
gulp.series(setTestEnv, async function runFontTest() {
await runTests("font");
gulp.series(setTestEnv, function runFontTest() {
return createTestSource("font");
})
);
@ -2000,34 +1914,31 @@ function createBaseline(done) {
gulp.task(
"unittestcli",
gulp.series(
setTestEnv,
"generic-legacy",
"lib-legacy",
function runUnitTestCli(done) {
const options = [
"node_modules/jasmine/bin/jasmine",
"JASMINE_CONFIG_PATH=test/unit/clitests.json",
];
const jasmineProcess = startNode(options, { stdio: "inherit" });
jasmineProcess.on("close", function (code) {
if (code !== 0) {
done(new Error("Unit tests failed."));
return;
}
done();
});
}
)
gulp.series(setTestEnv, "lib-legacy", function runUnitTestCli(done) {
const options = [
"node_modules/jasmine/bin/jasmine",
"JASMINE_CONFIG_PATH=test/unit/clitests.json",
];
const jasmineProcess = startNode(options, { stdio: "inherit" });
jasmineProcess.on("close", function (code) {
if (code !== 0) {
done(new Error("Unit tests failed."));
return;
}
done();
});
})
);
gulp.task("lint", function (done) {
console.log();
console.log("### Linting JS/CSS/JSON/SVG/HTML files");
console.log("### Linting JS/CSS/JSON files");
// Ensure that we lint the Firefox specific *.jsm files too.
const esLintOptions = [
"node_modules/eslint/bin/eslint",
"--ext",
".js,.jsm,.mjs,.json",
".",
"--report-unused-disable-directives",
];
@ -2047,21 +1958,13 @@ gulp.task("lint", function (done) {
const prettierOptions = [
"node_modules/prettier/bin/prettier.cjs",
"**/*.json",
"**/*.html",
];
if (process.argv.includes("--fix")) {
prettierOptions.push("--log-level", "error", "--write");
prettierOptions.push("--log-level", "silent", "--write");
} else {
prettierOptions.push("--log-level", "warn", "--check");
}
const svgLintOptions = [
"node_modules/svglint/bin/cli.js",
"**/*.svg",
"--ci",
"--no-summary",
];
const esLintProcess = startNode(esLintOptions, { stdio: "inherit" });
esLintProcess.on("close", function (esLintCode) {
if (esLintCode !== 0) {
@ -2082,19 +1985,8 @@ gulp.task("lint", function (done) {
done(new Error("Prettier failed."));
return;
}
const svgLintProcess = startNode(svgLintOptions, {
stdio: "inherit",
});
svgLintProcess.on("close", function (svgLintCode) {
if (svgLintCode !== 0) {
done(new Error("svglint failed."));
return;
}
console.log("files checked, no errors found");
done();
});
console.log("files checked, no errors found");
done();
});
});
});
@ -2133,15 +2025,6 @@ gulp.task(
)
);
gulp.task("dev-wasm", function () {
const VIEWER_WASM_OUTPUT = "web/wasm/";
fs.rmSync(VIEWER_WASM_OUTPUT, { recursive: true, force: true });
fs.mkdirSync(VIEWER_WASM_OUTPUT, { recursive: true });
return createWasmBundle().pipe(gulp.dest(VIEWER_WASM_OUTPUT));
});
gulp.task(
"dev-sandbox",
gulp.series(
@ -2177,13 +2060,6 @@ gulp.task(
gulp.series("locale")
);
},
function watchWasm() {
gulp.watch(
["external/openjpeg/*", "external/qcms/*"],
{ ignoreInitial: false },
gulp.series("dev-wasm")
);
},
function watchDevSandbox() {
gulp.watch(
[
@ -2288,8 +2164,6 @@ gulp.task("metalsmith", async function () {
.use(
layouts({
directory: "docs/templates",
pattern: "**",
transform: "nunjucks",
})
)
.use(relative())
@ -2325,9 +2199,8 @@ function packageJson() {
const DIST_NAME = "pdfjs-dist";
const DIST_DESCRIPTION = "Generic build of Mozilla's PDF.js library.";
const DIST_KEYWORDS = ["Mozilla", "pdf", "pdf.js"];
const DIST_HOMEPAGE = "https://mozilla.github.io/pdf.js/";
const DIST_HOMEPAGE = "http://mozilla.github.io/pdf.js/";
const DIST_BUGS_URL = "https://github.com/mozilla/pdf.js/issues";
const DIST_GIT_URL = "https://github.com/mozilla/pdf.js.git";
const DIST_LICENSE = "Apache-2.0";
const npmManifest = {
@ -2341,7 +2214,8 @@ function packageJson() {
bugs: DIST_BUGS_URL,
license: DIST_LICENSE,
optionalDependencies: {
"@napi-rs/canvas": "^0.1.81",
canvas: "^2.11.2",
path2d: "^0.2.0",
},
browser: {
canvas: false,
@ -2352,10 +2226,10 @@ function packageJson() {
},
repository: {
type: "git",
url: `git+${DIST_GIT_URL}`,
url: `git+https://github.com/mozilla/pdf.js.git`,
},
engines: {
node: ">=20.16.0 || >=22.3.0",
node: ">=18",
},
scripts: {},
};
@ -2379,8 +2253,23 @@ gulp.task(
"minified-legacy",
"types",
function createDist() {
console.log();
console.log("### Cloning baseline distribution");
fs.rmSync(DIST_DIR, { recursive: true, force: true });
fs.mkdirSync(DIST_DIR, { recursive: true });
safeSpawnSync("git", ["clone", "--depth", "1", DIST_REPO_URL, DIST_DIR]);
console.log();
console.log("### Overwriting all files");
// Remove all files/folders, except for `.git` because it needs to be a
// valid Git repository for the Git commands in the `dist` target to work.
for (const entry of fs.readdirSync(DIST_DIR)) {
if (entry !== ".git") {
fs.rmSync(DIST_DIR + entry, { recursive: true, force: true });
}
}
return ordered([
packageJson().pipe(gulp.dest(DIST_DIR)),
@ -2400,24 +2289,12 @@ gulp.task(
encoding: false,
})
.pipe(gulp.dest(DIST_DIR)),
gulp
.src(GENERIC_DIR + "web/iccs/**/*", {
base: GENERIC_DIR + "web",
encoding: false,
})
.pipe(gulp.dest(DIST_DIR)),
gulp
.src(GENERIC_DIR + "web/standard_fonts/**/*", {
base: GENERIC_DIR + "web",
encoding: false,
})
.pipe(gulp.dest(DIST_DIR)),
gulp
.src(GENERIC_DIR + "web/wasm/**/*", {
base: GENERIC_DIR + "web",
encoding: false,
})
.pipe(gulp.dest(DIST_DIR)),
gulp
.src(
[

Some files were not shown because too many files have changed in this diff Show More