pdf.js/web/firefoxcom.js

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

415 lines
12 KiB
JavaScript
Raw Normal View History

2012-09-01 07:48:21 +09:00
/* 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.
*/
import { isPdfFile, PDFDataRangeTransport } from "pdfjs-lib";
import { BaseExternalServices } from "./external_services.js";
import { BasePreferences } from "./preferences.js";
import { DEFAULT_SCALE_VALUE } from "./ui_utils.js";
import { L10n } from "./l10n.js";
import { PDFViewerApplication } from "./app.js";
Enable auto-formatting of the entire code-base using Prettier (issue 11444) Note that Prettier, purposely, has only limited [configuration options](https://prettier.io/docs/en/options.html). The configuration file is based on [the one in `mozilla central`](https://searchfox.org/mozilla-central/source/.prettierrc) with just a few additions (to avoid future breakage if the defaults ever changes). Prettier is being used for a couple of reasons: - To be consistent with `mozilla-central`, where Prettier is already in use across the tree. - To ensure a *consistent* coding style everywhere, which is automatically enforced during linting (since Prettier is used as an ESLint plugin). This thus ends "all" formatting disussions once and for all, removing the need for review comments on most stylistic matters. Many ESLint options are now redundant, and I've tried my best to remove all the now unnecessary options (but I may have missed some). Note also that since Prettier considers the `printWidth` option as a guide, rather than a hard rule, this patch resorts to a small hack in the ESLint config to ensure that *comments* won't become too long. *Please note:* This patch is generated automatically, by appending the `--fix` argument to the ESLint call used in the `gulp lint` task. It will thus require some additional clean-up, which will be done in a *separate* commit. (On a more personal note, I'll readily admit that some of the changes Prettier makes are *extremely* ugly. However, in the name of consistency we'll probably have to live with that.)
2019-12-25 23:59:37 +09:00
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
throw new Error(
'Module "./firefoxcom.js" shall not be used outside MOZCENTRAL builds.'
);
}
class FirefoxCom {
/**
* Creates an event that the extension is listening for and will
* asynchronously respond to.
* @param {string} action - The action to trigger.
* @param {Object|string} [data] - The data to send.
* @returns {Promise<any>} A promise that is resolved with the response data.
*/
static requestAsync(action, data) {
return new Promise(resolve => {
this.request(action, data, resolve);
});
}
/**
* Creates an event that the extension is listening for and will, optionally,
* asynchronously respond to.
* @param {string} action - The action to trigger.
* @param {Object|string} [data] - The data to send.
*/
static request(action, data, callback = null) {
const request = document.createTextNode("");
if (callback) {
request.addEventListener(
"pdf.js.response",
event => {
const response = event.detail.response;
event.target.remove();
2012-08-02 07:31:25 +09:00
callback(response);
},
{ once: true }
);
}
document.documentElement.append(request);
2012-08-02 07:31:25 +09:00
const sender = new CustomEvent("pdf.js.message", {
bubbles: true,
cancelable: false,
detail: {
action,
data,
responseExpected: !!callback,
},
});
request.dispatchEvent(sender);
}
}
2013-07-13 03:14:13 +09:00
class DownloadManager {
#openBlobUrls = new WeakMap();
downloadUrl(url, filename, options = {}) {
FirefoxCom.request("download", {
originalUrl: url,
filename,
options,
});
}
2015-02-03 00:12:52 +09:00
downloadData(data, filename, contentType) {
const blobUrl = URL.createObjectURL(
new Blob([data], { type: contentType })
);
FirefoxCom.request("download", {
blobUrl,
originalUrl: blobUrl,
filename,
isAttachment: true,
});
}
2013-07-13 03:14:13 +09:00
/**
* @returns {boolean} Indicating if the data was opened.
*/
openOrDownloadData(data, filename, dest = null) {
const isPdfData = isPdfFile(filename);
const contentType = isPdfData ? "application/pdf" : "";
if (isPdfData) {
let blobUrl = this.#openBlobUrls.get(data);
if (!blobUrl) {
blobUrl = URL.createObjectURL(new Blob([data], { type: contentType }));
this.#openBlobUrls.set(data, blobUrl);
}
// Let Firefox's content handler catch the URL and display the PDF.
// NOTE: This cannot use a query string for the filename, see
// https://bugzilla.mozilla.org/show_bug.cgi?id=1632644#c5
let viewerUrl = blobUrl + "#filename=" + encodeURIComponent(filename);
if (dest) {
viewerUrl += `&filedest=${escape(dest)}`;
}
try {
window.open(viewerUrl);
return true;
} catch (ex) {
console.error(`openOrDownloadData: ${ex}`);
// Release the `blobUrl`, since opening it failed, and fallback to
// downloading the PDF file.
URL.revokeObjectURL(blobUrl);
this.#openBlobUrls.delete(data);
}
}
this.downloadData(data, filename, contentType);
return false;
}
download(blob, url, filename, options = {}) {
const blobUrl = URL.createObjectURL(blob);
FirefoxCom.request("download", {
blobUrl,
originalUrl: url,
filename,
options,
});
}
}
2013-11-19 07:51:06 +09:00
class Preferences extends BasePreferences {
async _readFromStorage(prefObj) {
return FirefoxCom.requestAsync("getPreferences", prefObj);
}
}
(function listenFindEvents() {
const events = [
"find",
"findagain",
"findhighlightallchange",
"findcasesensitivitychange",
"findentirewordchange",
"findbarclose",
"finddiacriticmatchingchange",
];
const findLen = "find".length;
const handleEvent = function ({ type, detail }) {
if (!PDFViewerApplication.initialized) {
return;
}
if (type === "findbarclose") {
PDFViewerApplication.eventBus.dispatch(type, { source: window });
return;
}
PDFViewerApplication.eventBus.dispatch("find", {
source: window,
type: type.substring(findLen),
query: detail.query,
caseSensitive: !!detail.caseSensitive,
entireWord: !!detail.entireWord,
highlightAll: !!detail.highlightAll,
findPrevious: !!detail.findPrevious,
matchDiacritics: !!detail.matchDiacritics,
});
};
for (const event of events) {
window.addEventListener(event, handleEvent);
}
})();
(function listenZoomEvents() {
const events = ["zoomin", "zoomout", "zoomreset"];
const handleEvent = function ({ type, detail }) {
if (!PDFViewerApplication.initialized) {
return;
}
// Avoid attempting to needlessly reset the zoom level *twice* in a row,
// when using the `Ctrl + 0` keyboard shortcut.
if (
type === "zoomreset" &&
PDFViewerApplication.pdfViewer.currentScaleValue === DEFAULT_SCALE_VALUE
) {
return;
}
PDFViewerApplication.eventBus.dispatch(type, { source: window });
};
for (const event of events) {
window.addEventListener(event, handleEvent);
}
})();
(function listenSaveEvent() {
const handleEvent = function ({ type, detail }) {
if (!PDFViewerApplication.initialized) {
return;
}
PDFViewerApplication.eventBus.dispatch("download", { source: window });
};
window.addEventListener("save", handleEvent);
})();
(function listenEditingEvent() {
const handleEvent = function ({ detail }) {
if (!PDFViewerApplication.initialized) {
return;
}
PDFViewerApplication.eventBus.dispatch("editingaction", {
source: window,
name: detail.name,
});
};
window.addEventListener("editingaction", handleEvent);
})();
if (PDFJSDev.test("GECKOVIEW")) {
(function listenQueryEvents() {
window.addEventListener("pdf.js.query", async ({ detail: { queryId } }) => {
let result = null;
if (queryId === "canDownloadInsteadOfPrint") {
result = false;
const { pdfDocument, pdfViewer } = PDFViewerApplication;
if (pdfDocument) {
try {
const hasUnchangedAnnotations =
pdfDocument.annotationStorage.size === 0;
// WillPrint is called just before printing the document and could
// lead to have modified annotations.
const hasWillPrint =
pdfViewer.enableScripting &&
!!(await pdfDocument.getJSActions())?.WillPrint;
const hasUnchangedOptionalContent = (
await pdfViewer.optionalContentConfigPromise
).hasInitialVisibility;
result =
hasUnchangedAnnotations &&
!hasWillPrint &&
hasUnchangedOptionalContent;
} catch {
console.warn("Unable to check if the document can be downloaded.");
}
}
}
window.dispatchEvent(
new CustomEvent("pdf.js.query.answer", {
bubbles: true,
cancelable: false,
detail: {
queryId,
value: result,
},
})
);
});
})();
}
class FirefoxComDataRangeTransport extends PDFDataRangeTransport {
requestDataRange(begin, end) {
FirefoxCom.request("requestDataRange", { begin, end });
}
[Firefox] Don't send the "abortLoading" message synchronously Despite the comment, I believe that changing this should be fine for two separate reasons: - The platform code has an "unload" event listener, see [this code](https://searchfox.org/mozilla-central/rev/edb2612db13e89f1c44ab95b1e4d4366c16eb9fb/toolkit/components/pdfjs/content/PdfStreamConverter.sys.mjs#533-538), that invokes the same method. Hence we should still be guaranteed that the relevant platform method will run. - The `FirefoxComDataRangeTransport.abort` method is never actually invoked in the Firefox PDF Viewer. Note that the [`PDFDataRangeTransport.abort` method](https://github.com/mozilla/pdf.js/blob/f4b396f6c88e951174879a52390fa89223326b36/src/display/api.js#L759) is only invoked via the [`PDFDataTransportStream.cancelAllRequests` method](https://github.com/mozilla/pdf.js/blob/f4b396f6c88e951174879a52390fa89223326b36/src/display/transport_stream.js#L167-L175), which in turn is only invoked via the [`WorkerTransport.destroy` method](https://github.com/mozilla/pdf.js/blob/f4b396f6c88e951174879a52390fa89223326b36/src/display/api.js#L2485-L2487). That method is invoked via the [`PDFDocumentLoadingTask.destroy` method](https://github.com/mozilla/pdf.js/blob/f4b396f6c88e951174879a52390fa89223326b36/src/display/api.js#L630), which in the viewer is only invoked via the [`PDFViewerApplication.close` method](https://github.com/mozilla/pdf.js/blob/f4b396f6c88e951174879a52390fa89223326b36/web/app.js#L919) which is never actually called in the Firefox PDF Viewer. All-in-all, given the existing platform code *and* the current viewer-implementation it should thus be safe to not wait for the "abortLoading" message to complete.
2023-11-26 20:18:17 +09:00
// NOTE: This method is currently not invoked in the Firefox PDF Viewer.
abort() {
[Firefox] Don't send the "abortLoading" message synchronously Despite the comment, I believe that changing this should be fine for two separate reasons: - The platform code has an "unload" event listener, see [this code](https://searchfox.org/mozilla-central/rev/edb2612db13e89f1c44ab95b1e4d4366c16eb9fb/toolkit/components/pdfjs/content/PdfStreamConverter.sys.mjs#533-538), that invokes the same method. Hence we should still be guaranteed that the relevant platform method will run. - The `FirefoxComDataRangeTransport.abort` method is never actually invoked in the Firefox PDF Viewer. Note that the [`PDFDataRangeTransport.abort` method](https://github.com/mozilla/pdf.js/blob/f4b396f6c88e951174879a52390fa89223326b36/src/display/api.js#L759) is only invoked via the [`PDFDataTransportStream.cancelAllRequests` method](https://github.com/mozilla/pdf.js/blob/f4b396f6c88e951174879a52390fa89223326b36/src/display/transport_stream.js#L167-L175), which in turn is only invoked via the [`WorkerTransport.destroy` method](https://github.com/mozilla/pdf.js/blob/f4b396f6c88e951174879a52390fa89223326b36/src/display/api.js#L2485-L2487). That method is invoked via the [`PDFDocumentLoadingTask.destroy` method](https://github.com/mozilla/pdf.js/blob/f4b396f6c88e951174879a52390fa89223326b36/src/display/api.js#L630), which in the viewer is only invoked via the [`PDFViewerApplication.close` method](https://github.com/mozilla/pdf.js/blob/f4b396f6c88e951174879a52390fa89223326b36/web/app.js#L919) which is never actually called in the Firefox PDF Viewer. All-in-all, given the existing platform code *and* the current viewer-implementation it should thus be safe to not wait for the "abortLoading" message to complete.
2023-11-26 20:18:17 +09:00
FirefoxCom.request("abortLoading", null);
}
}
class FirefoxScripting {
static async createSandbox(data) {
const success = await FirefoxCom.requestAsync("createSandbox", data);
if (!success) {
throw new Error("Cannot create sandbox.");
}
}
static async dispatchEventInSandbox(event) {
FirefoxCom.request("dispatchEventInSandbox", event);
}
static async destroySandbox() {
FirefoxCom.request("destroySandbox", null);
}
}
class ExternalServices extends BaseExternalServices {
updateFindControlState(data) {
FirefoxCom.request("updateFindControlState", data);
}
updateFindMatchesCount(data) {
FirefoxCom.request("updateFindMatchesCount", data);
}
initPassiveLoading(callbacks) {
let pdfDataRangeTransport;
window.addEventListener("message", function windowMessage(e) {
if (e.source !== null) {
// The message MUST originate from Chrome code.
console.warn("Rejected untrusted message from " + e.origin);
return;
}
const args = e.data;
if (typeof args !== "object" || !("pdfjsLoadAction" in args)) {
return;
}
switch (args.pdfjsLoadAction) {
case "supportsRangedLoading":
if (args.done && !args.data) {
callbacks.onError();
break;
}
pdfDataRangeTransport = new FirefoxComDataRangeTransport(
args.length,
args.data,
args.done,
args.filename
Enable auto-formatting of the entire code-base using Prettier (issue 11444) Note that Prettier, purposely, has only limited [configuration options](https://prettier.io/docs/en/options.html). The configuration file is based on [the one in `mozilla central`](https://searchfox.org/mozilla-central/source/.prettierrc) with just a few additions (to avoid future breakage if the defaults ever changes). Prettier is being used for a couple of reasons: - To be consistent with `mozilla-central`, where Prettier is already in use across the tree. - To ensure a *consistent* coding style everywhere, which is automatically enforced during linting (since Prettier is used as an ESLint plugin). This thus ends "all" formatting disussions once and for all, removing the need for review comments on most stylistic matters. Many ESLint options are now redundant, and I've tried my best to remove all the now unnecessary options (but I may have missed some). Note also that since Prettier considers the `printWidth` option as a guide, rather than a hard rule, this patch resorts to a small hack in the ESLint config to ensure that *comments* won't become too long. *Please note:* This patch is generated automatically, by appending the `--fix` argument to the ESLint call used in the `gulp lint` task. It will thus require some additional clean-up, which will be done in a *separate* commit. (On a more personal note, I'll readily admit that some of the changes Prettier makes are *extremely* ugly. However, in the name of consistency we'll probably have to live with that.)
2019-12-25 23:59:37 +09:00
);
callbacks.onOpenWithTransport(pdfDataRangeTransport);
break;
case "range":
pdfDataRangeTransport.onDataRange(args.begin, args.chunk);
break;
case "rangeProgress":
pdfDataRangeTransport.onDataProgress(args.loaded);
break;
case "progressiveRead":
pdfDataRangeTransport.onDataProgressiveRead(args.chunk);
// Don't forget to report loading progress as well, since otherwise
// the loadingBar won't update when `disableRange=true` is set.
pdfDataRangeTransport.onDataProgress(args.loaded, args.total);
break;
case "progressiveDone":
pdfDataRangeTransport?.onDataProgressiveDone();
break;
case "progress":
callbacks.onProgress(args.loaded, args.total);
break;
case "complete":
if (!args.data) {
callbacks.onError(args.errorCode);
break;
}
callbacks.onOpenWithData(args.data, args.filename);
break;
}
});
FirefoxCom.request("initPassiveLoading", null);
}
reportTelemetry(data) {
FirefoxCom.request("reportTelemetry", JSON.stringify(data));
}
updateEditorStates(data) {
FirefoxCom.request("updateEditorStates", data);
}
async createL10n() {
const [localeProperties] = await Promise.all([
FirefoxCom.requestAsync("getLocaleProperties", null),
document.l10n.ready,
]);
return new L10n(localeProperties, document.l10n);
}
createScripting() {
return FirefoxScripting;
}
async getNimbusExperimentData() {
if (!PDFJSDev.test("GECKOVIEW")) {
return null;
}
const nimbusData = await FirefoxCom.requestAsync(
"getNimbusExperimentData",
null
);
return nimbusData && JSON.parse(nimbusData);
}
}
export { DownloadManager, ExternalServices, FirefoxCom, Preferences };