clarkk clarkk - 1 month ago 45
Javascript Question

Print PDF in Firefox

How to print a PDF in Firefox?

This function works in Chrome but not in Firefox

function print_pdf(url){
var id = 'iframe', html = '<iframe id="'+id+'" src="'+url+'" style="display:none"></iframe>';
$('#main').append(html);
$('#'+id).load(function(){
document.getElementById(id).contentWindow.print();
}
}


error



Error: Permission denied to access property "print"

Answer

Firefox: Permission denied to access property "print"

This is a bug in firefox. Locally it can be disabled by going to about:config and set the property of pdfjs.disabled to true. Only possible workaround is to use a server-side script and modify the pdf. Using php you could use fpdf and embed extensions to implement js (inclunding the print() function) or simply convert the pdf to an image, return the url and print it. You could use FPDI to modify the existing pdf. I will give you an example on how I got it to work with PHP.

Generating a PDF file with inline javascript (autoprint) using FPDI and PDF_JS

require_once('fpdf.php');
require_once('fpdi.php');

class PDF_JavaScript extends FPDI {

    var $javascript;
    var $n_js;

    function IncludeJS($script) {
        $this->javascript=$script;
    }

    function _putjavascript() {
        $this->_newobj();
        $this->n_js=$this->n;
        $this->_out('<<');
        $this->_out('/Names [(EmbeddedJS) '.($this->n+1).' 0 R]');
        $this->_out('>>');
        $this->_out('endobj');
        $this->_newobj();
        $this->_out('<<');
        $this->_out('/S /JavaScript');
        $this->_out('/JS '.$this->_textstring($this->javascript));
        $this->_out('>>');
        $this->_out('endobj');
    }

    function _putresources() {
        parent::_putresources();
        if (!empty($this->javascript)) {
            $this->_putjavascript();
        }
    }

    function _putcatalog() {
        parent::_putcatalog();
        if (!empty($this->javascript)) {
            $this->_out('/Names <</JavaScript '.($this->n_js).' 0 R>>');
        }
    }
}

class PDF_AutoPrint extends PDF_JavaScript
{
    function AutoPrint($dialog=false)
    {
        //Open the print dialog or start printing immediately on the standard printer
        $param=($dialog ? 'true' : 'false');
        $script="print($param);";
        $this->IncludeJS($script);
    }

    function AutoPrintToPrinter($server, $printer, $dialog=false)
    {
        $script = "document.contentWindow.print();";
        $this->IncludeJS($script);
    }
}

$pdf=new PDF_AutoPrint();
$pdf->setSourceFile("mozilla.pdf");
//Open the print dialog
$tplIdx = $pdf->importPage(1, '/MediaBox');
$pdf->addPage();
$pdf->useTemplate($tplIdx, 10, 10, 90);
$pdf->AutoPrint(true);
$pdf->Output('generated.pdf', 'F');

Now you can simply append the generated pdf to your page and the included javascript will call the print() function. You do not even have to call it manually anymore. However, in firefox this will only work with visibility: hidden and not with display: none.

function print_pdf(url){
    var iFrameJQueryObject = $('<iframe id="iframe" src="'+url+'" style="visibility: hidden"></iframe>');
    $('#foo').append(iFrameJQueryObject);
}
print_pdf('mozilla_generated.pdf');

Chrome: Security Error (cross-origin)

The pdf should be located at the same host. Firefox was okay with other domains in my tests, but chrome gave me cross-origin errors.


Firefox: Printed page includes about:blank only

You will get an empty page in firefox (jsfiddle), because it will print the iframe before it has loaded any content. Mentioned methods like $(document).onload() won't help, since they only wait for the DOM to load and setTimeout() can still result in errors, since you do not know how long it takes the iFrame to load.

You can simply resolve this issue by using jQuery's load(). (doc) This will give you the possibility to use a callback function as parameter.

if a "complete" callback is provided, it is executed after post-processing and HTML insertion has been performed. The callback is fired once for each element in the jQuery collection, and this is set to each DOM element in turn.

Code Example 1

function print_pdf(url){
    var id = 'iframe', html = '<iframe id="'+id+'" src="'+url+'" style="display:none"></iframe>';
    $('body').append(html);
    // wait for the iFrame to fully load and call the print() function afterwards
    $('#' + id).load(function () {
        document.getElementById(id).contentWindow.print();
    });
}

Alternatively you could directly create an jQuery object and use jQuery's on() (doc) to attach any event handler.

Code Example 2 (jsfiddle)

function print_pdf(url){
    var iFrameJQueryObject = $('<iframe id="iframe" src="'+url+'" style="display:none"></iframe>');
    $('body').append(iFrameJQueryObject);
    iFrameJQueryObject.on('load', function(){
        $(this).get(0).contentWindow.print();
    });
}