clarkk
clarkk

Reputation: 1

print a pdf via iframe (cross domain)

I need to print a PDF... But I get an error

Is there a workaround? I just need to print a PDF file with one click

error:

Uncaught SecurityError: Blocked a frame with origin "https://secure.domain.com" from accessing a frame with origin "https://cdn.domain.com". Protocols, domains, and ports must match.

code:

var iframe = $('<iframe src="'+url+'" style="display:none"></iframe>').appendTo($('#main')).load(function(){
    iframe.get(0).contentWindow.print();
});

Upvotes: 13

Views: 19624

Answers (6)

ᴍᴇʜᴏᴠ
ᴍᴇʜᴏᴠ

Reputation: 5276

I needed to print a PDF embedded through a data:application/pdf;base64,… iframe, and I ran into the same cross-origin issue.

The solution was to convert the Base64 contents that I had into a blob, and then use put blob's object URL into the iframe src. After doing that I was able to print that iframe.

I know link-only answers are discouraged, but copying someone else's answers into my own didn't feel right either.

Upvotes: 3

David Votrubec
David Votrubec

Reputation: 4166

You do not need proxy server for workaround. You can create proxy iframe and then dynamically create another iframe inside the proxy iframe. Then attach onload="print()" to it.

Something like this

  /**
   * Load iframe from cross-origin via proxy iframe
   * and then invokes the print dialog.
   * It is not possible to call window.print() on the target iframe directly
   * because of cross-origin policy.
   * 
   * Downside is that the iframe stays loaded. 
   */
  function printIframe(url) {
    var proxyIframe = document.createElement('iframe');
    var body = document.getElementsByTagName('body')[0];
    body.appendChild(proxyIframe);
    proxyIframe.style.width = '100%';
    proxyIframe.style.height = '100%';
    proxyIframe.style.display = 'none';

    var contentWindow = proxyIframe.contentWindow;
    contentWindow.document.open();
    // Set dimensions according to your needs.
    // You may need to calculate the dynamically after the content has loaded
    contentWindow.document.write('<iframe src="' + url + '" onload="print();" width="1000" height="1800" frameborder="0" marginheight="0" marginwidth="0">');
    contentWindow.document.close();
  }

Upvotes: 2

Safeer Hussain
Safeer Hussain

Reputation: 1340

There is a workaround for this.

  1. Create an endpoint in your server to return the HTML content of the external url. (because you can't get external content from the browser - same-origin policy)

  2. Use $.get to fetch the external content from your URL and append it to an iframe.

Something similar to this:

HTML:

<div id="main">
    <iframe id="my-iframe" style="display:none"></iframe>
</div>

JS:

$.get('https://secure.domain.com/get-cdndomaincom-url-content', function(response) {
    var iframe = $('#my-iframe')[0],
        iframedoc = iframe.contentDocument || iframe.contentWindow.document;

    iframedoc.body.innerHTML = response;
    iframe.contentWindow.print();
});

C# implementation for get-cdndomaincom-url-content:

Easiest way to read from a URL into a string in .NET

Upvotes: 2

Saurabh Chaturvedi
Saurabh Chaturvedi

Reputation: 2176

It is a CORS issue . There is a library that acts as a CORS alternative , you can find it here Xdomain CORS alternative Github . It kind of by-passes CORS request seamlessly to render cross domain resources effectively.

It has a Javascript, AngularJS, as well as a JQuery wrapper too . I think this will provide you a more elegant solution, and is simple to integrate. Give it a try .

Upvotes: 0

Michael Warner
Michael Warner

Reputation: 4247

--Issue--

HiDeo is right this is a cross-domain issue. It is a part of CORS which is a great thing for the security of the web but also a pain.

--Philosophy--

There are ways to work around CORS but I believe in finding a solution that works for most to all cases and keep using it. This creates easier code to reason about and keeps code consistent rather then changing and creating code for edge cases. This can create a harder initial solution but as you can reuse the method regardless of use case you end up saving time.

--Answer--

The way our team handles cross domain request issues is bypassing it completely. CORS is something for browsers only. So the best way to solve all cases of this issue is don't give the reasonability to the browser. We have the server fetch the document and giving it to the browser on the same domain.

(I'm an Angular guy) The client would say something like

$http.get('/pdf/x').then(function(){
    //do whatever you want with any cross-domain doument
});

The Server would have something like what you see here HTTP GET Request in Node.js Express

Upvotes: 0

HiDeoo
HiDeoo

Reputation: 10563

The error you are dealing with is related to cross-domain protection and the same-origin policy.

In your case, you can print an cross-domain iframe if you nest this iframe in another local iframe that we can call a proxy iframe.

Since the proxy iframe is local and have the same origin, you can print it without any issue and it'll also print the cross-domain iframe.

See below for an example:

index.html (container)

$(function() {
  var url = 'proxy.html'; // We're not loading the PDF but a proxy which will load the PDF in another iframe.

  var iframe = $('<iframe src="' + url + '"></iframe>').appendTo($('#main'));

  iframe.on('load', function(){
    iframe.get(0).contentWindow.print();
  });
});

proxy.html (proxy)

<body>
  <iframe src="http://ANOTHER_DOMAIN/PDF_NAME.pdf"></iframe>
</body>

With this solution, you no longer have cross-domain issues and you can use the print() function. The only things you need to deal with are a way to pass the PDF url from the container to the proxy and a way to detect when the iframe with the PDF is actually loaded but these depends on the solution / languages you're using.

Upvotes: 7

Related Questions