Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

window.print() crashes if a 'print' event listener causes a rerender (Chrome, DEV-mode only) #16734

Closed
mwszekely opened this issue Sep 10, 2019 · 14 comments · Fixed by #19220
Closed

Comments

@mwszekely
Copy link

Do you want to request a feature or report a bug?
Report a bug.

What is the current behavior?
Programmatically calling window.print() can cause React to report strange errors before crashing under certain circumstances. The trigger seems to be a call to print() that results in a React state change somewhere (which, in Chrome, seems to happen because the print preview it shows can cause media query events, which can be hooked up to calls to a setState function). This does not always happen, however--as shown in the example if the code is not called from in a setTimeout it doesn't seem to crash. In addition, if the user initiates printing instead (e.g. via CTRL+P), React never crashes.

The two errors I've seen happen as a result of this are Failed to execute 'handleEvent' on 'EventListener': The provided callback is no longer runnable (needs "Pause on caught exceptions" in Chrome's DevTools to catch), and after a few of those they're followed by Maximum update depth exceeded (even though the setState function is only called once). Once this happens, there's a good chance the tab become completely unresponsive after the print dialog is closed.

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem.

A minimal example can be found here: https://codesandbox.io/s/immutable-snowflake-06cj2 . There are no external dependencies. The code is commented with instructions to reproduce the behavior.

I've also included a copy of the code here for completeness' sake:

import React from "react";
import ReactDOM from "react-dom";

// Custom hook to match media queries.
// The setMatch call in this function is at the bottom of the error stack.
function useMediaQuery(query) {

  const [match, setMatch] = React.useState(() => window.matchMedia(query).matches);

  React.useEffect(() => {
    const queryList = window.matchMedia(query);

    /*************************************************************/
    /* If this function is not called, the crash does not occur. */
    /* When window.print is called, this effect is run as a      */
    /* consequence. However React handles it causes errors and   */
    /* other strange behavior. Because Firefox handles printing  */
    /* differently, it does not crash.                           */
    /* While this is an example of using setState in useEffect,  */
    /* it is only run once before the error is thrown anyway.    */
    /*************************************************************/
    const handleMatch = () => setMatch(queryList.matches);
    handleMatch();

    // Bookkeeping, not relevant to the crash.
    queryList.addListener(handleMatch);
    return () => queryList.removeListener(handleMatch);
  }, [query]);

  return match;
}


export function App() {
  // This is the simplist media query that will cause a crash
  // But anything will work as long as it's different during printing.
  // For example, "(max-width: 1260px)" also works if the window
  // is wide enough.
  let isPrint = useMediaQuery("print");

  return (
    <div>
      <p style={{ fontWeight: 'bold' }}>Important: Only Chrome crashes; Firefox (and likely others) are safe.</p>
      <p>(You may need to open this page in its own window instead of the Code Sandbox split-screen.)</p>

      <button onClick={() => setTimeout(window.print, 100)}>Crashes</button>
      <button onClick={asyncPrint}>Crashes</button>
      <button onClick={() => window.print()}>Does not crash</button>
      <button onClick={syncPrint}>Does not crash</button>
      <p>(Note that pressing CTRL+P never crashes)</p>
      <p>If you click the button to call window.print(), React will behave strangely. Once the print preview is about to show, suddenly an error <code>"Failed to execute 'handleEvent' on 'EventListener': The provided callback is no longer runnable"</code> will be thrown. After that happens a few times, a <code>"Maximum update depth exceeded"</code> error will be thrown, even though the setState function is only called once.</p>
      <p>(Printing with CTRL+P won't crash, clicking the button to print will)</p>
    </div>
  );
}


function syncPrint() { window.print(); }
function asyncPrint() { setTimeout(window.print, 100); }


const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

What is the expected behavior?
React should not throw errors after calling window.print(), even if doing so causes state changes.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

This is the latest release (16.9.0). I am unsure if other versions of React are affected.
The behavior was tested using Chrome 76 on Windows 10. This behavior will not happen in Firefox and likely other browsers as well, probably due to the unique way Chrome handles printing and print previews.

@threepointone
Copy link
Contributor

Thank you for the detailed reproduction steps and code! Seems odd indeed, we'll have a look.

@RobertLbebber
Copy link

Same issue on Chrome 72 with traditional components rather than hooks

@zachary-nguyen
Copy link

Took another approach to the async call and it seems that the print really doesn't work well with async.

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function asyncPrint() {
  await sleep(10000);
  window.print();
}

However, calling onclick={() =>{setTimeout(window.print(),100)}} does give the desired outcome.
Looking at the error message it seems like the setTimeout is causing some sort of overflow?

@sophiebits
Copy link
Collaborator

sophiebits commented Sep 21, 2019

This is actually a Chromium bug. I ran into it a few months ago and filed https://bugs.chromium.org/p/chromium/issues/detail?id=956832 for it. It should only affect React in development mode, not the production build.

That said, React can work around it if we choose. Specifically, this .dispatchEvent call will fail silently in this case and callCallback never gets called (dispatchEvent just returns without doing anything):

fakeNode.dispatchEvent(evt);

React could detect that case and fall back to a try/catch implementation for invokeGuardedCallback instead. (Might also make sense to handle the case where .dispatchEvent throws, though it didn't seem that's what's happening here.) Though I was hoping Chromium would just fix the issue so React doesn't need a workaround.

@sophiebits
Copy link
Collaborator

(cc @stubbornella for the Chrome bug)

@aaqibshehzad
Copy link

I have come across this same bug. Does anyone have a workaround or will the fix be out soon.

@sophiebits
Copy link
Collaborator

@aaqibshehzad Unfortunately there's no easy workaround for applications right now. We need to wait for the Chrome bug to be fixed or add a workaround in React.

However, this only affects the development build of React. So at least it should work correctly in your production builds.

@sophiebits sophiebits changed the title Calling window.print() can cause React to crash under specific conditions Crash calling window.print() if a 'print' event listener causes a rerender (Chrome, DEV-mode only) Apr 6, 2020
@sophiebits sophiebits changed the title Crash calling window.print() if a 'print' event listener causes a rerender (Chrome, DEV-mode only) window.print() crashes if a 'print' event listener causes a rerender (Chrome, DEV-mode only) Apr 6, 2020
@gaearon
Copy link
Collaborator

gaearon commented Jun 30, 2020

#19220

gaearon added a commit to gaearon/react that referenced this issue Jun 30, 2020
gaearon added a commit that referenced this issue Jul 1, 2020
* Fix development mode hang when iframe is removed

* Also fix #16734
@yoyo837
Copy link

yoyo837 commented Jul 14, 2020

Same issue here, waiting for the fix to be released.

@yoyo837
Copy link

yoyo837 commented Jul 14, 2020

Hang:

setTimeout(() => {
  window.print();
}, number);

or

this.setState(() => ({
  xxx: 123
}),
() => {
  window.print();
});

Works well:

window.print();

@gaearon
Copy link
Collaborator

gaearon commented Jul 14, 2020

The fix will likely make it into 17 and not any 16.x release. Although maybe we could backport if there is an urgent common need. This only happens in development mode so I don’t think it is an urgent problem.

@yoyo837
Copy link

yoyo837 commented Jul 15, 2020

Got it, thanks!

@jamespagedev
Copy link

The fix will likely make it into 17 and not any 16.x release. Although maybe we could backport if there is an urgent common need. This only happens in development mode so I don’t think it is an urgent problem.

Actually this is happening for me in deployment mode as well. I threw up a stack overflow question here > https://stackoverflow.com/questions/63428865/change-state-before-and-after-print-for-displaying-only-on-print

@gaearon
Copy link
Collaborator

gaearon commented Aug 16, 2020

The problem reported in this issue should be fixed in React 17 RC.
https://reactjs.org/blog/2020/08/10/react-v17-rc.html

If you still experience it, please raise a new issue with a reproducing example.

@facebook facebook locked as resolved and limited conversation to collaborators Aug 16, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants