Commit 6eae8df6 authored by Emilio Cobos Álvarez's avatar Emilio Cobos Álvarez
Browse files

Bug 1663140 - Don't block on window.print() if there are print callbacks. r=smaug, a=RyanVM

Not really a fan of this, but I can't think of a better alternative
really... Ideas welcome :)

The main issue is that in bug 1662975 we made window.print() not return
until the user has closed the print / print preview dialog (as it is
needed for some use cases). This matches other browsers, too.

We use an nsAutoSyncOperation here, in order not to violate the
run-to-completion invariants, which turns off micro-tasks, timers,
etc... However we'd still want promises inside mozPrintCallback to
resolve and such, which is a bit contradictory. It is really awkward to
have this behavior change based on whether we have a print callback...

Differential Revision: https://phabricator.services.mozilla.com/D89298
parent 844d0d75
......@@ -1264,6 +1264,7 @@ Document::Document(const char* aContentType)
mAllowDNSPrefetch(true),
mIsStaticDocument(false),
mCreatingStaticClone(false),
mHasPrintCallbacks(false),
mInUnlinkOrDeletion(false),
mHasHadScriptHandlingObject(false),
mIsBeingUsedAsImage(false),
......
......@@ -2633,6 +2633,13 @@ class Document : public nsINode,
IsStaticDocument();
}
void SetHasPrintCallbacks() {
MOZ_DIAGNOSTIC_ASSERT(IsStaticDocument());
mHasPrintCallbacks = true;
}
bool HasPrintCallbacks() const { return mHasPrintCallbacks; }
/**
* Register/Unregister the ActivityObserver into mActivityObservers to listen
* the document's activity changes such as OnPageHide, visibility, activity.
......@@ -4383,6 +4390,10 @@ class Document : public nsINode,
// True while this document is being cloned to a static document.
bool mCreatingStaticClone : 1;
// True if this static document has any <canvas> element with a
// mozPrintCallback property at the time of the clone.
bool mHasPrintCallbacks : 1;
// True iff the document is being unlinked or deleted.
bool mInUnlinkOrDeletion : 1;
......
......@@ -5295,7 +5295,9 @@ Nullable<WindowProxyHolder> nsGlobalWindowOuter::Print(
nsCOMPtr<nsIContentViewer> cv;
RefPtr<BrowsingContext> bc;
if (docToPrint->IsStaticDocument() && bool(aIsPreview)) {
bool hasPrintCallbacks = false;
if (docToPrint->IsStaticDocument() && aIsPreview == IsPreview::Yes) {
MOZ_DIAGNOSTIC_ASSERT(aBlockUntilDone == BlockUntilDone::No);
// We're already a print preview window, just reuse our browsing context /
// content viewer.
//
......@@ -5322,8 +5324,8 @@ Nullable<WindowProxyHolder> nsGlobalWindowOuter::Print(
bc = aDocShellToCloneInto->GetBrowsingContext();
} else {
AutoNoJSAPI nojsapi;
auto printKind =
bool(aIsPreview) ? PrintKind::PrintPreview : PrintKind::Print;
auto printKind = aIsPreview == IsPreview::Yes ? PrintKind::PrintPreview
: PrintKind::Print;
aError = OpenInternal(EmptyString(), EmptyString(), EmptyString(),
false, // aDialog
false, // aContentModal
......@@ -5386,6 +5388,8 @@ Nullable<WindowProxyHolder> nsGlobalWindowOuter::Print(
return nullptr;
}
hasPrintCallbacks |= clone->HasPrintCallbacks();
// Do this now so that we get a script handling object, and thus can
// create our clones.
aError = cv->SetDocument(clone);
......@@ -5413,6 +5417,8 @@ Nullable<WindowProxyHolder> nsGlobalWindowOuter::Print(
if (NS_WARN_IF(NS_FAILED(rv))) {
continue;
}
hasPrintCallbacks |= doc && doc->HasPrintCallbacks();
}
}
}
......@@ -5424,7 +5430,7 @@ Nullable<WindowProxyHolder> nsGlobalWindowOuter::Print(
return nullptr;
}
if (bool(aIsPreview)) {
if (aIsPreview == IsPreview::Yes) {
aError = webBrowserPrint->PrintPreview(aPrintSettings, aListener,
std::move(aPrintPreviewCallback));
if (aError.Failed()) {
......@@ -5435,8 +5441,13 @@ Nullable<WindowProxyHolder> nsGlobalWindowOuter::Print(
webBrowserPrint->Print(aPrintSettings, aListener);
}
if (bool(aBlockUntilDone)) {
// Wait until print document is closed.
// When aBlockUntilDone is true, we usually want to block until the print
// dialog is hidden. But we can't really do that if we have print callbacks,
// because we are inside a sync operation, and we want to run microtasks / etc
// that the print callbacks may create.
//
// It is really awkward to have this subtle behavior difference...
if (aBlockUntilDone == BlockUntilDone::Yes && !hasPrintCallbacks) {
SpinEventLoopUntil([&] { return bc->IsDiscarded(); });
}
......
......@@ -529,11 +529,16 @@ HTMLCanvasElement* HTMLCanvasElement::GetOriginalCanvas() {
nsresult HTMLCanvasElement::CopyInnerTo(HTMLCanvasElement* aDest) {
nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
NS_ENSURE_SUCCESS(rv, rv);
if (aDest->OwnerDoc()->IsStaticDocument()) {
Document* destDoc = aDest->OwnerDoc();
if (destDoc->IsStaticDocument()) {
// The Firefox print preview code can create a static clone from an
// existing static clone, so we may not be the original 'canvas' element.
aDest->mOriginalCanvas = GetOriginalCanvas();
if (GetMozPrintCallback()) {
destDoc->SetHasPrintCallbacks();
}
// We make sure that the canvas is not zero sized since that would cause
// the DrawImage call below to return an error, which would cause printing
// to fail.
......
......@@ -13,6 +13,10 @@ support-files =
file_page_change_print_original_2.html
skip-if = os == "mac"
[browser_window_print.js]
support-files =
file_window_print.html
[browser_preview_in_container.js]
support-files =
file_print.html
......
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const TEST_PATH = getRootDirectory(gTestPath).replace(
"chrome://mochitests/content",
"https://example.com"
);
add_task(async function test() {
// window.print() only shows print preview when print.tab_modal.enabled is
// true.
await SpecialPowers.pushPrefEnv({
set: [["print.tab_modal.enabled", true]],
});
is(
document.querySelector(".printPreviewBrowser"),
null,
"There shouldn't be any print preview browser"
);
await BrowserTestUtils.withNewTab(
`${TEST_PATH}file_window_print.html`,
async function(browser) {
info(
"Waiting for the first window.print() to run and ensure we're showing the preview..."
);
await BrowserTestUtils.waitForCondition(
() => !!document.querySelector(".printPreviewBrowser")
);
{
let [before, afterFirst] = await SpecialPowers.spawn(
browser,
[],
() => {
return [
!!content.document.getElementById("before-print"),
!!content.document.getElementById("after-first-print"),
];
}
);
ok(before, "Content before printing should be in the DOM");
ok(!afterFirst, "Shouldn't have returned yet from window.print()");
}
gBrowser.getTabDialogBox(browser).abortAllDialogs();
await BrowserTestUtils.waitForCondition(
() => !!document.querySelector(".printPreviewBrowser")
);
{
let [before, afterFirst, afterSecond] = await SpecialPowers.spawn(
browser,
[],
() => {
return [
!!content.document.getElementById("before-print"),
!!content.document.getElementById("after-first-print"),
!!content.document.getElementById("after-second-print"),
];
}
);
ok(before, "Content before printing should be in the DOM");
ok(afterFirst, "Should be in the second print already");
ok(afterSecond, "Shouldn't have blocked if we have mozPrintCallbacks");
}
}
);
});
<!doctype html>
<div id="before-print">Before print</div>
<canvas id="canvas" width="100" height="100"></canvas>
<script>
onload = function() {
// window.print() is special until after the load event is finished firing.
setTimeout(function() {
// This one should block until we're done printing.
window.print();
document.body.insertAdjacentHTML('beforeend', `<div id="after-first-print">After first print</div>`);
let canvas = document.getElementById("canvas");
canvas.mozPrintCallback = function() {};
// This one shouldn't, because the print callbacks need to run.
window.print();
document.body.insertAdjacentHTML('beforeend', `<div id="after-second-print">After second print</div>`);
}, 0);
}
</script>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment