react-to-print icon indicating copy to clipboard operation
react-to-print copied to clipboard

Prints only once in the latest macOS Safari

Open odragora opened this issue 3 years ago • 21 comments

Using the official example: https://codesandbox.io/s/rzdhd First time I try to print the printing dialog shows up as expected. If I cancel it and try to print the second time, a popup is displayed saying that the document is already sent to printer. If I click OK, the printing dialog shows up with the page to print being empty.

macOS 11.4, Safari 14.1.1

odragora avatar Jul 05 '21 20:07 odragora

I have the same issue +1 with same version, but it happen not only with this lib but with every window.print()

barvhaim avatar Jul 05 '21 21:07 barvhaim

As far as I know, the same problem already happened before. I think it was due to the Safari popup not blocking JS code execution, causing popular libraries to remove the elements from the page before they were actually sent to print after user clicks Confirm.

Seems like the workarounds are not working anymore in new versions of Safari. This problem also present in Print.is library.

Or maybe it's just a new issue.

odragora avatar Jul 05 '21 21:07 odragora

It seems something has changed with Safari 14.1 that breaks this. I'll look for a solution but there may not be one. I did find that refreshing the page solves the problem, so maybe as a hopefully temporary workaround you could refresh the page in the afterPrint callback. Unfortunately there is no way to know in this callback if the user pressed "Cancel" or "Print". If anyone learns more please let me know

MatthewHerbst avatar Jul 06 '21 06:07 MatthewHerbst

It seems something has changes with Safari 14.1 that breaks this. I'll look for a solution but there may not be one. I did find that refreshing the page solves the problem, so maybe as a hopefully temporary workaround you could refresh the page in the afterPrint callback. Unfortunately there is no way to know in this callback if the user pressed "Cancel" or "Print". If anyone learns more please let me know

Thank you for looking into this. Unfortunately, page refresh is usually not an option for React views, but I don't know any better solution.

odragora avatar Jul 06 '21 10:07 odragora

Any update on this?

Ahmad-Zahid avatar Aug 02 '21 10:08 Ahmad-Zahid

Hello. I don't have an update yet unfortunately. I don't know what is causing the second print to show distorted. We previously were able to avoid the Safari modal completely, but it seems the Safari change is now impacting multiple printing libraries such as Print.js, not just this one. I am actively looking for solutions

MatthewHerbst avatar Aug 03 '21 05:08 MatthewHerbst

Hey @MatthewHerbst I am on Safari Version 15.0 (15612.1.29.41.4, 15612) and this issue still persists. Would like to share a work around I was able to come up. The workaround here works in lieu of useReactToPrint hook

I was inspired by this stackoverflow thread whereby I open another popup window and render the div (componentRef) contents. Then using the popup window document to print instead of main document.

Here's high level idea if anyone wants to replicate:

Code ```javascript // browser detection const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); ...

// Print handler for Safari const getSafariPrintHandler = (componentRef, documentTitle) => () => { const popupWindow = window.open( url, documentTitle, toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=750, height=450 );; const printDoc = popupWindow.document; printDoc.write(<!DOCTYPE> <html> <head> <title>${documentTitle}</title> </head> <body> ${componentRef.current.innerHTML} </body> </html>);

// apply the default print styles here: https://github.com/gregnb/react-to-print/blob/master/src/index.tsx#L29 const styleEl = printDoc.createElement('style'); styleEl.appendChild(printDoc.createTextNode(pagePrintStyleDefault)); printDoc.head.appendChild(styleEl);

// copy styles logic from react-to-print copyStyles(printDoc);

// clean up printDoc.close(); popupWindow.print(); popupWindow.close(); }; .... // finally, the wrapper hook export const usePrint = ({ componentRef, ...otherConfig}) => { const handlePrint = useReactToPrint({ content: () => componentRef.current, ...otherConfig }); if (isSafari) { return getSafariPrintHandler(componentRef); }

return handlePrint; };

</details>

The usage is similar to `useReactToPrint` hook documented in this module.

vikramkamath avatar Oct 13 '21 19:10 vikramkamath

Thanks @vikramkamath! I'll try and see if I can adapt some of that into the library. PRs are also always welcome 😄

MatthewHerbst avatar Oct 14 '21 19:10 MatthewHerbst

Related: https://github.com/crabbly/Print.js/issues/528

MatthewHerbst avatar Dec 26 '21 19:12 MatthewHerbst

@vikramkamath Do you have working version of code ?

Can you please share here

Thanks in advance

darshakeyan avatar Feb 15 '22 10:02 darshakeyan

@darshak369 I have created a sandbox which does not use useReactToPrint hook but only bits of code from react-to-print module. You can download the files as zip locally and run it. Note: This demo won't print on any other browser as I am only checking for Safari. For other browsers, I use useReactToPrint out of the box. Let me know if you encounter any issues.

vikikamath avatar Feb 16 '22 03:02 vikikamath

@vikramkamath Thanks for your solution it is working fine I have encountered with other issue in your code which is related to document styles

I am facing <link> warning which you have write in else block. I want to add link tag to my target document how will i do that in your code.

Is their any ways to add link tag also to my document so my style also replicate while downloading PDF using safari Browser ?

darshakeyan avatar Feb 16 '22 08:02 darshakeyan

@darshak369 in my case I wasn't relying on <link href ... /> for styles hence I handled it like that. The warning is appropriate in your case as the print doc relies on <link> tags for styles. You can add the following block in the copyStyles() that should apply styles from <link> tags: https://github.com/gregnb/react-to-print/blob/master/src/index.tsx#L441-L468

tldr; The copyStyles() was created from src/index.tsx and I skipped loading any styles from the <link> part.

vikikamath avatar Feb 16 '22 15:02 vikikamath

@vikramkamath Thanks for the information

I am able to load the <link> tag correctly now it is not showing me any kind of warning related to the <link>.

This is the code that i had modified in copyStyles(), is there something else we have to add in getSafariPrintHandler()?

Code
const copyStyles = targetDocument => {
  const headEls = document.querySelectorAll("style, link[rel='stylesheet']");
  for (let i = 0, headElsLen = headEls.length; i < headElsLen; i += 1) {
    const node = headEls[i];
    if (node.tagName === 'STYLE') {
      console.log('Loading Style Tag...');
      const styleEl = targetDocument.createElement(node.tagName);
      const { sheet } = node;
      if (sheet) {
        let styleCSS = '';
        for (let j = 0, cssLen = sheet.cssRules.length; j < cssLen; j += 1) {
          if (typeof sheet.cssRules[j].cssText === 'string') {
            styleCSS += `${sheet.cssRules[j].cssText}\r\n`;
          }
        }
        styleEl.appendChild(targetDocument.createTextNode(styleCSS));
        targetDocument.head.appendChild(styleEl);
      }
    } else if (node.tagName === 'LINK' && node.getAttribute('href')) {
      console.log('Loading Link Tag...');
      const newHeadEl = targetDocument.createElement(node.tagName);
      for (let j = 0, attrLen = node.attributes.length; j < attrLen; j += 1) {
        // eslint-disable-line max-len
        const attr = node.attributes[j];
        if (attr) {
          newHeadEl.setAttribute(attr.nodeName, attr.nodeValue || '');
        }
      }
      targetDocument.head.appendChild(newHeadEl);
    } else {
      console.log('Warning.....');
      // eslint-disable-next-line no-console
      console.warn(
        'Encountered invalid HTML. This can cause problems in many browsers, and so the <link> was not loaded. The <link> is:',
        node,
      );
    }
  }
};

After clicking on the button nothing happens

darshakeyan avatar Feb 17 '22 05:02 darshakeyan

@darshak369 you forgot to wrap the <link> node code in the else block:

if (node.tagName === 'STYLE') {
  // This code is correct
} else { // This is what you are forgetting
  // All the code below the `<link> nodes` comment needs to be in here
}

MatthewHerbst avatar Feb 17 '22 09:02 MatthewHerbst

Here is the new update I am able to load the <link> and <style> tags correctly. I had debugged the code and I found that the document has <link> and <style> tags inside <head> tag.

Logs of link and style tags -

[Log] print doc #document
<!DOCTYPE >
<html>
<head>
<title>Darshak-Shark (Copy)-Sourcing</title>
<style>…</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link href="https://domain.com/vendors.css" rel="stylesheet">
<link href="https://domain.com/styles.css" rel="stylesheet">
<style>…</style>
<style>…</style>
<style></style>
<style>@media print { @page { size: portrait; } html, body { height: 99%; } }</style>
</head>
<body>…</body>
</html>

@vikramkamath @MatthewHerbst any idea why is this happening although it is calling function correctly yet not opening a pop-up window.

darshakeyan avatar Feb 18 '22 04:02 darshakeyan

@darshak369 if you could make a working codesandbox that would be very helpful please to help debugging, it's very difficult to do it with comments

MatthewHerbst avatar Feb 19 '22 05:02 MatthewHerbst

@MatthewHerbst Thanks for your response

It will be very time consuming to convert code into codesandbox for me.

I have downloaded those HTML codes to my system and run them locally apparently It solved my previous issue related to formatting. the new issue that I was facing now is window pop up closes immediately after opening in Chrome Browser and Safari as well.

I was going through the https://github.com/gregnb/react-to-print/blob/master/src/index.tsx#L441-L468 code from repo.

I have found I forget to write markLoaded() function, I am not sure what to add in copyStyle() function.

Can you please suggest to me which code will solve my issue!

Above Comment Has CopyStyle() function which run fine with <style> tag.

Thanks for your time

darshakeyan avatar Feb 21 '22 06:02 darshakeyan

@darshak369 found any solution ? After trying with -

if (node.tagName === 'STYLE') {
  // This code is correct
} else { // This is what you are forgetting
  // All the code below the `<link> nodes` comment needs to be in here
}

the window pop-up and closes immediately after opening in Safari.

VishalGithub11 avatar May 09 '22 11:05 VishalGithub11

if I comment copystyles(), the print dialog is being open with HTML, but it excludes all styles.

const styleEl = printDoc.createElement("style");
styleEl.appendChild(printDoc.createTextNode(pagePrintStyleDefault));
printDoc.head.appendChild(styleEl);

//  copyStyles(printDoc);

printDoc.close();
popupWindow.print();

VishalGithub11 avatar May 10 '22 06:05 VishalGithub11

Hey folks.

I came across this issue and figured out a way to work around it by reading some threads about the topic

I created a hook that attaches a div element to a new browser window and runs the print command from the new windows. By doing it, if the users cancel the printing flow, the next time they hit the button the safari dialog asking if they really want to print it won't appear and the issue doesn't happen.

export const useBrowserPrint = () => {
  const print = (element: HTMLDivElement, updateCallerKeyCallback: () => void) => {
    const newWindow = window.open()

    newWindow!.document.write('<!DOCTYPE html')
    newWindow!.document.write('<html>')
    newWindow!.document.write('<head>')
    newWindow!.document.write('</head>')
    newWindow!.document.write('<body>')
    newWindow!.document.write('</body>')
    newWindow!.document.write('</html>')

    newWindow!.document.body.appendChild(element)

    const parentHead = window.document.querySelector('head')!.childNodes
    parentHead.forEach((item) => {
      newWindow!.document.head.appendChild(item.cloneNode(true))
    })

    setTimeout(() => {
      newWindow!.document.close()
      newWindow!.focus()
      newWindow!.print()
      newWindow!.close()
    }, 250)
    updateCallerKeyCallback()
  }

  return { print }
}

This callback function is to update the caller key and rerender it on the screen cuz it's been removed out of the DOM when attached to the new window. This is the code present in the component I want to print:

const { print } = useBrowserPrint()

const [printableKey, updatePrintableKey] = useState<number>(0)
const forcePrintableKeyUpdate = useCallback(() => updatePrintableKey(Math.random()), [])

const printableRef = useRef<HTMLDivElement>(null)
const handlePrint = () => print(printableRef.current!, forcePrintableKeyUpdate)

return (
  <div key={printableKey} className="container self-center">
    <div className="flex flex-col items-center">
      <ActionBar onClick={handlePrint} />
      <div ref={printableRef} className="w-full flex flex-col items-center mt-4">
      ....

The code is not using the library but maybe it can help on defining a definitive solution. It works fine on Safari, Firefox and Chrome

Farrael22 avatar Jun 17 '22 18:06 Farrael22

This issue appears resolved by Apple using Safari 16.4, MacOS 13.3

MatthewHerbst avatar Apr 11 '23 19:04 MatthewHerbst

We observe same issue with Safari 17.3.1, MacOS 14.3.1

drekinov avatar Feb 12 '24 10:02 drekinov

@drekinov could you please share steps to reproduce, and let me know what version of react-to-print you are using? I'm testing react-to-print v2.15.0 and the same MacOS/Safari versions, and am unable to reproduce the issue.

I do see this message pop up, but clicking "Print" or clicking "Cancel" and then printing again (which brings up the box again) do seem to work fine

Screenshot 2024-02-12 at 11 02 47 AM

MatthewHerbst avatar Feb 12 '24 19:02 MatthewHerbst

Ah, sorry we had bothered you. Probably did not understand the root issue here. We observe the same as you did. We are not mac / safari primary users. We got customer complain that this message appear. It is a bit annoying because of Single page application each print after first cancel trigger it.. Comparing with chrome and other browsers we thought that message is the issue discussed here.

drekinov avatar Feb 12 '24 19:02 drekinov

Ah, no problem. And yeah, nothing we can do about Safari popping that message box unfortunately. I don't fully understand why they do it honestly, it's not like it's preventing malicious behavior somehow 🤷 Hopefully one day there is a more formal Print API that solves these browser-specific oddities. Please do keep letting us know about dev/customer feedback though, it's very much appreciated!

MatthewHerbst avatar Feb 12 '24 19:02 MatthewHerbst