Skip to content

Commit a19ee72

Browse files
dholbertrvandermeulen
authored andcommitted
Bug 1980356: Don't block in window.print() if there's a registered mozPrintCallback (e.g. for PDF.js) and if any sort of print dialog might be up. a=RyanVM DONTBUILD
Before this patch, we would unconditionally block in window.print() if the user had the system print dialog enabled. That was problematic because it prevents mozPrintCallback from doing asynchronous work, as described in the nearby code-comment, and this resulted in PDFs printing blank for such users if they used the "print" button (which triggered window.print()). This patch makes the print.prefer_system_dialog codepath share the same logic flow as the regular-print-preview codepath -- now we will *not* block if there's a registered print callback (which allows the callback's microtasks to proceed while the print dialog is still up). Original Revision: https://phabricator.services.mozilla.com/D261296 Differential Revision: https://phabricator.services.mozilla.com/D262701
1 parent 67e874e commit a19ee72

File tree

3 files changed

+73
-11
lines changed

3 files changed

+73
-11
lines changed

dom/base/nsGlobalWindowOuter.cpp

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5158,23 +5158,29 @@ Nullable<WindowProxyHolder> nsGlobalWindowOuter::Print(
51585158
}
51595159
}
51605160

5161-
// When using window.print() with the new UI, we usually want to block until
5162-
// the print dialog is hidden. But we can't really do that if we have print
5163-
// callbacks, because we are inside a sync operation, and we want to run
5164-
// microtasks / etc that the print callbacks may create. It is really awkward
5165-
// to have this subtle behavior difference...
5166-
//
5167-
// We also want to do this for fuzzing, so that they can test window.print().
5161+
// Check whether we're in a case where we need to block in order for
5162+
// window.print() to function properly:
51685163
const bool shouldBlock = [&] {
51695164
if (aForWindowDotPrint == IsForWindowDotPrint::No) {
5165+
// We're not doing window.print; no need to block.
51705166
return false;
51715167
}
5172-
if (aIsPreview == IsPreview::Yes) {
5168+
5169+
// When window.print() spawns a print dialog (either our own tab-modal
5170+
// dialog or the system-print dialog), we usually want window.print() to
5171+
// block until the print dialog is hidden. But we can't really do that if
5172+
// we have print callbacks (mozPrintCallback), because we are inside a sync
5173+
// operation, and we want to run microtasks / etc that the print callbacks
5174+
// may create. It is really awkward to have this subtle behavior
5175+
// difference...
5176+
if (aIsPreview == IsPreview::Yes ||
5177+
StaticPrefs::print_prefer_system_dialog()) {
51735178
return !hasPrintCallbacks;
51745179
}
5175-
if (StaticPrefs::print_prefer_system_dialog()) {
5176-
return true;
5177-
}
5180+
5181+
// We also want to allow window.print() to block for fuzzing, so that
5182+
// fuzzers can test either behavior without needing to interact with a
5183+
// dialog.
51785184
return StaticPrefs::dom_window_print_fuzzing_block_while_printing();
51795185
}();
51805186

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[window-print-mozPrintCallback-async-001.html]
2+
prefs: [print.always_print_silent:true, "print_printer:Mozilla Save to PDF", print.prefer_system_dialog:true]
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<!doctype html>
2+
<meta charset="utf-8">
3+
<title>
4+
Test for window.print(), with mozPrintCallback making async call to obj.done
5+
</title>
6+
<script src="/resources/testharness.js"></script>
7+
<script src="/resources/testharnessreport.js"></script>
8+
<canvas id="myCanvas" width="80" height="80"></canvas>
9+
<script>
10+
let t = async_test(
11+
"window.print(), with mozPrintCallback making async call to obj.done"
12+
);
13+
14+
// mozPrintCallback needs to ensure that obj.done() will be called, as a signal
15+
// that it's done with its rendering work. Here, we make that call via an async
16+
// task, scheduled via setTimeout, to mimic the asynchronous work that's
17+
// involved in real-life mozPrintCallback scenarios (e.g. PDF.js waiting for
18+
// assets like fonts to be ready before it can render the content).
19+
myCanvas.mozPrintCallback = function (obj) {
20+
setTimeout(() => {
21+
obj.done();
22+
}, 0);
23+
};
24+
25+
// window.print shouldn't block the main thread for any substantial time here.
26+
//
27+
// To validate this expectation, we see how soon after calling window.print()
28+
// we're able to execute another task on the main thread (scheduled via
29+
// requestAnimationFrame), and we expect the delay to be (substantially) less
30+
// than this TOO_LONG_WINDOW_PRINT_DUR threshold. (In bug 1980356, we were
31+
// consistently overshooting this, because window.print would spawn a task that
32+
// would block the main thread until the page print timer "watchdog" kicked in
33+
// after ~10 seconds.)
34+
const TOO_LONG_WINDOW_PRINT_DUR = 5000; // 5 seconds
35+
36+
window.addEventListener("load", () => {
37+
let timestampBeforePrint = performance.now();
38+
window.print();
39+
40+
// See how soon we're able to execute another task, scheduled via rAF:
41+
requestAnimationFrame(
42+
t.step_func(function () {
43+
let timestampAfterPrint = performance.now();
44+
let printDur = timestampAfterPrint - timestampBeforePrint;
45+
assert_less_than(
46+
printDur,
47+
TOO_LONG_WINDOW_PRINT_DUR,
48+
"window.print shouldn't block main thread for too long."
49+
);
50+
t.done();
51+
})
52+
);
53+
});
54+
</script>

0 commit comments

Comments
 (0)