From 1731c0fb42869b515241ba9adc02f8cfb4b3cb85 Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Tue, 10 Sep 2013 18:17:26 +0200 Subject: [PATCH 1/2] Add mozPrintCallback shim This shim does the following: 1. Intercept window.print() 2. For a window.print() call (if allowed, ie. no previous print job): 1. Dispatch the beforeprint event. 2. Render a printg progress dialog. 3. For each canvas, call mozPrintCallback if existent (one at a time, async). 4. During each mozPrintCallback callback, update the progress dialog. 5. When all es have been rendered, invoke the real window.print(). 6. Dispatch the afterprint event. The shim is not included in Firefox through the preprocessor. Keyboard shortcuts (Ctrl/Cmd + P) are intercepted and default behavior (i.e. printing) is prevented, and the steps for window.print() are run. window.attachEvent is used, in order to cancel printing in IE10 and earlier (courtesy of Stack Overflow - http://stackoverflow.com/a/15302847). Unfortunately, this doesn't work in IE11 - if Ctrl + P is used, the print dialog will be shown twice: Once because of Ctrl + P, and again when all pages have finished rendering. This logic of this polyfill is not specific to PDF.js, so it can also be used in other projects. There's one additional modification in PDF.js's viewer.js: The printed element is wrapped in a
. This is needed, because Chrome would otherwise print one canvas on two pages. --- web/mozPrintCallback_polyfill.js | 141 ++++++++++++++++++ web/page_view.js | 6 +- ...wer-snippet-mozPrintCallback-polyfill.html | 71 +++++++++ web/viewer.html | 3 + web/viewer.js | 5 + 5 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 web/mozPrintCallback_polyfill.js create mode 100644 web/viewer-snippet-mozPrintCallback-polyfill.html diff --git a/web/mozPrintCallback_polyfill.js b/web/mozPrintCallback_polyfill.js new file mode 100644 index 000000000..c5eeb3548 --- /dev/null +++ b/web/mozPrintCallback_polyfill.js @@ -0,0 +1,141 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +/* Copyright 2013 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. + */ +/* globals HTMLCanvasElement */ + +'use strict'; +(function mozPrintCallbackPolyfillClosure() { + if ('mozPrintCallback' in document.createElement('canvas')) { + return; + } + // Cause positive result on feature-detection: + HTMLCanvasElement.prototype.mozPrintCallback = undefined; + + var canvases; // During print task: non-live NodeList of elements + var index; // Index of element that is being processed + + var print = window.print; + window.print = function print() { + if (canvases) { + console.warn('Ignored window.print() because of a pending print job.'); + return; + } + try { + dispatchEvent('beforeprint'); + } finally { + canvases = document.querySelectorAll('canvas'); + index = -1; + next(); + } + }; + + function dispatchEvent(eventType) { + var event = document.createEvent('CustomEvent'); + event.initCustomEvent(eventType, false, false, 'custom'); + window.dispatchEvent(event); + } + + function next() { + if (!canvases) { + return; // Print task cancelled by user (state reset in abort()) + } + + renderProgress(); + if (++index < canvases.length) { + var canvas = canvases[index]; + if (typeof canvas.mozPrintCallback === 'function') { + canvas.mozPrintCallback({ + context: canvas.getContext('2d'), + abort: abort, + done: next + }); + } else { + next(); + } + } else { + renderProgress(); + print.call(window); + setTimeout(abort, 20); // Tidy-up + } + } + + function abort() { + if (canvases) { + canvases = null; + renderProgress(); + dispatchEvent('afterprint'); + } + } + + function renderProgress() { + var progressContainer = document.getElementById('mozPrintCallback-shim'); + if (canvases) { + var progress = Math.round(100 * index / canvases.length); + var progressBar = progressContainer.querySelector('progress'); + var progressPerc = progressContainer.querySelector('.relative-progress'); + progressBar.value = progress; + progressPerc.textContent = progress + '%'; + progressContainer.removeAttribute('hidden'); + progressContainer.onclick = abort; + } else { + progressContainer.setAttribute('hidden', ''); + } + } + + var hasAttachEvent = !!document.attachEvent; + + window.addEventListener('keydown', function(event) { + if (event.keyCode === 80/*P*/ && (event.ctrlKey || event.metaKey)) { + window.print(); + if (hasAttachEvent) { + // Only attachEvent can cancel Ctrl + P dialog in IE <=10 + // attachEvent is gone in IE11, so the dialog will re-appear in IE11. + return; + } + event.preventDefault(); + if (event.stopImmediatePropagation) { + event.stopImmediatePropagation(); + } else { + event.stopPropagation(); + } + return; + } + if (event.keyCode === 27 && canvases) { // Esc + abort(); + } + }, true); + if (hasAttachEvent) { + document.attachEvent('onkeydown', function(event) { + event = event || window.event; + if (event.keyCode === 80/*P*/ && event.ctrlKey) { + event.keyCode = 0; + return false; + } + }); + } + + if ('onbeforeprint' in window) { + // Do not propagate before/afterprint events when they are not triggered + // from within this polyfill. (FF/IE). + var stopPropagationIfNeeded = function(event) { + if (event.detail !== 'custom' && event.stopImmediatePropagation) { + event.stopImmediatePropagation(); + } + }; + window.addEventListener('beforeprint', stopPropagationIfNeeded, false); + window.addEventListener('afterprint', stopPropagationIfNeeded, false); + } +})(); diff --git a/web/page_view.js b/web/page_view.js index 124998b33..3d70074fe 100644 --- a/web/page_view.js +++ b/web/page_view.js @@ -525,7 +525,11 @@ var PageView = function pageView(container, id, scale, CustomStyle.setProp('transformOrigin' , canvas, '0% 0%'); var printContainer = document.getElementById('printContainer'); - printContainer.appendChild(canvas); + var canvasWrapper = document.createElement('div'); + canvasWrapper.style.width = viewport.width + 'pt'; + canvasWrapper.style.height = viewport.height + 'pt'; + canvasWrapper.appendChild(canvas); + printContainer.appendChild(canvasWrapper); var self = this; canvas.mozPrintCallback = function(obj) { diff --git a/web/viewer-snippet-mozPrintCallback-polyfill.html b/web/viewer-snippet-mozPrintCallback-polyfill.html new file mode 100644 index 000000000..ebf2e3419 --- /dev/null +++ b/web/viewer-snippet-mozPrintCallback-polyfill.html @@ -0,0 +1,71 @@ + diff --git a/web/viewer.html b/web/viewer.html index c970b3236..3eb22ab06 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -309,5 +309,8 @@ limitations under the License.
+ + + diff --git a/web/viewer.js b/web/viewer.js index 5cb315fde..dcfd46738 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -56,6 +56,11 @@ PDFJS.imageResourcesPath = './images/'; var mozL10n = document.mozL10n || document.webL10n; //#include ui_utils.js + +//#if !(FIREFOX || MOZCENTRAL || B2G) +//#include mozPrintCallback_polyfill.js +//#endif + //#if GENERIC || CHROME //#include download_manager.js //#endif From 194a734e5dbed221a6d7356246ccf4bf5447e14c Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Wed, 2 Oct 2013 00:03:32 +0200 Subject: [PATCH 2/2] Resolved severe memory leak (mozPrintCallback) Do NOT save the temporary element as `this.canvas`. `PDFViewer.pages[i].canvas` appears to be used to generate the thumbnail icons. Well, that's no justification for preventing GC of those temporary elements used during mozPrintCallback. With a document consisting of 79 pages, this resulted in a 600-700MB leaked memory. --- web/page_view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/page_view.js b/web/page_view.js index 3d70074fe..2acfb7094 100644 --- a/web/page_view.js +++ b/web/page_view.js @@ -514,7 +514,7 @@ var PageView = function pageView(container, id, scale, // Use the same hack we use for high dpi displays for printing to get better // output until bug 811002 is fixed in FF. var PRINT_OUTPUT_SCALE = 2; - var canvas = this.canvas = document.createElement('canvas'); + var canvas = document.createElement('canvas'); canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE; canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE; canvas.style.width = (PRINT_OUTPUT_SCALE * viewport.width) + 'pt';