safari not allowing a second window.print()

2019-04-28 16:12发布

问题:

Hello I am running into a weird issue in Safari. I have a button and when clicked it prints the content of the html. My issue is that when calling on window.print() the first time it works great, however, on the second click it will display a popup stating:

'This webpage is trying to print. Do you want to print this webpage?'

If I click Print in this dialog nothing happens. Any ideas why this could be happening? Thank you in advance!

Javascript -

$scope.print = function() {
    var contents = document.getElementById("print-section").outerHTML;
    var frame1 = document.createElement('iframe');
    frame1.name = "frame3";
    frame1.style.position = "absolute";
    frame1.style.top = "-1000000px";
    document.body.appendChild(frame1);

    var frameDoc = frame1.contentWindow ? frame1.contentWindow : frame1.contentDocument.document ? frame1.contentDocument.document : frame1.contentDocument;
    frameDoc.document.open();
    frameDoc.document.write('<html><head>'); // add some libraries for the new document
    frameDoc.document.write('</head><body>');
    frameDoc.document.write(contents);
    frameDoc.document.write('</body></html>');
    frameDoc.document.close();
    setTimeout(function () {
        window.frames["frame3"].focus();
        window.frames["frame3"].print();
        document.body.removeChild(frame1);
    }, 500);

    return false;
};

Html-

<div id="print-section">
   <div>Section to print<>
</div>

回答1:

After some investigation I found a solution.

Safari shows the print warning only if you "Cancel" the print. However the reason for the second blank print is that you remove too quickly the content using

document.body.removeChild(frame1)

If you increase the waiting time from 500ms to 5 secs you give the user the time to close the warning popup and print.

In my case I succesfully tested on Chrome/FF/Edge/Safari this jQuery plugin, increasing also in this library the removal timeout https://github.com/DoersGuild/jQuery.print



回答2:

As Matteo Conta correctly mentioned, the problem is that Safari's print confirmation dialog does not stop JS code execution flow to wait for printing.
Therefore, what we really want is to detect when exactly does a print action end in order to run a cleanup absolutely safely.

setTimeout solution is a good starting point but I wanted to tackle the problem more reliably and have come up with an event-based solution using matchMedia and onfocus to catch the exact moment when printing is finished (either cancelled or completed).

Below is the code that I've tested for this particular question (ES5, iframe printing).
Also, I've created a Gist on GitHub with a generic approach demonstrated. Hope it will be useful.

$scope.print = function () {
  var frame = appendFrame();

  // Safari
  if (!window.onafterprint) {
    // emulate onbeforeprint/onafterprint events
    var mediaQueryCallback = function (mql) {
      if (!mql.matches && frame) {
        document.body.removeChild(frame);
      }
    };
    var mediaQueryList = window.frames[frame.name].matchMedia('print');
    mediaQueryList.addListener(mediaQueryCallback);

    // the code below will trigger a cleanup in case a user hits Cancel button
    // in that Safari's new additional print confirmation dialog
    window.frames[frame.name].focus();
    window.frames[frame.name].onfocus = function () {
      return mediaQueryCallback(mediaQueryList);
    };
  }

  window.frames[frame.name].print();

  return false;
};

For brevity I've extracted the frame creation code into a trivial function (appendFrame) and omitted setTimeout call from the original question (after frameDoc.document.close(); line).

var appendFrame = function () {
  var contents = document.getElementById("print-section").outerHTML;
  var frame1 = document.createElement('iframe');
  frame1.name = "frame3";
  frame1.style.position = "absolute";
  frame1.style.top = "-1000000px";
  document.body.appendChild(frame1);

  var frameDoc = frame1.contentWindow ? frame1.contentWindow : frame1.contentDocument.document ? frame1.contentDocument.document : frame1.contentDocument;
  frameDoc.document.open();
  frameDoc.document.write('<html><head>'); // add some libraries for the new document
  frameDoc.document.write('</head><body>');
  frameDoc.document.write(contents);
  frameDoc.document.write('</body></html>');
  frameDoc.document.close();

  return frame1;
};

inspired by MDN article:

onbeforeprint