mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-17 23:35:34 +00:00
Bug 1710887 - Make modal state handling in window.print() more reliable. r=smaug
If afterprint removes the window we're printing, we'd call LeaveModalState() without a top window, and never leave the modal state. Differential Revision: https://phabricator.services.mozilla.com/D115007
This commit is contained in:
parent
e2075d6bdd
commit
d973da3734
@ -5237,6 +5237,20 @@ void nsGlobalWindowOuter::PrintOuter(ErrorResult& aError) {
|
||||
#endif
|
||||
}
|
||||
|
||||
class MOZ_RAII AutoModalState {
|
||||
public:
|
||||
explicit AutoModalState(nsGlobalWindowOuter& aWin)
|
||||
: mModalStateWin(aWin.EnterModalState()) {}
|
||||
|
||||
~AutoModalState() {
|
||||
if (mModalStateWin) {
|
||||
mModalStateWin->LeaveModalState();
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<nsGlobalWindowOuter> mModalStateWin;
|
||||
};
|
||||
|
||||
Nullable<WindowProxyHolder> nsGlobalWindowOuter::Print(
|
||||
nsIPrintSettings* aPrintSettings, nsIWebProgressListener* aListener,
|
||||
nsIDocShell* aDocShellToCloneInto, IsPreview aIsPreview,
|
||||
@ -5265,8 +5279,7 @@ Nullable<WindowProxyHolder> nsGlobalWindowOuter::Print(
|
||||
}
|
||||
|
||||
nsAutoSyncOperation sync(docToPrint, SyncOperationBehavior::eAllowInput);
|
||||
EnterModalState();
|
||||
auto exitModal = MakeScopeExit([&] { LeaveModalState(); });
|
||||
AutoModalState modalState(*this);
|
||||
|
||||
nsCOMPtr<nsIContentViewer> cv;
|
||||
RefPtr<BrowsingContext> bc;
|
||||
@ -6327,14 +6340,14 @@ void nsGlobalWindowOuter::ReallyCloseWindow() {
|
||||
CleanUp();
|
||||
}
|
||||
|
||||
void nsGlobalWindowOuter::EnterModalState() {
|
||||
nsGlobalWindowOuter* nsGlobalWindowOuter::EnterModalState() {
|
||||
// GetInProcessScriptableTop, not GetInProcessTop, so that EnterModalState
|
||||
// works properly with <iframe mozbrowser>.
|
||||
nsGlobalWindowOuter* topWin = GetInProcessScriptableTopInternal();
|
||||
|
||||
if (!topWin) {
|
||||
NS_ERROR("Uh, EnterModalState() called w/o a reachable top window?");
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If there is an active ESM in this window, clear it. Otherwise, this can
|
||||
@ -6389,33 +6402,38 @@ void nsGlobalWindowOuter::EnterModalState() {
|
||||
}
|
||||
}
|
||||
topWin->mModalStateDepth++;
|
||||
return topWin;
|
||||
}
|
||||
|
||||
void nsGlobalWindowOuter::LeaveModalState() {
|
||||
nsGlobalWindowOuter* topWin = GetInProcessScriptableTopInternal();
|
||||
{
|
||||
nsGlobalWindowOuter* topWin = GetInProcessScriptableTopInternal();
|
||||
if (!topWin) {
|
||||
NS_WARNING("Uh, LeaveModalState() called w/o a reachable top window?");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!topWin) {
|
||||
NS_WARNING("Uh, LeaveModalState() called w/o a reachable top window?");
|
||||
return;
|
||||
if (topWin != this) {
|
||||
MOZ_ASSERT(IsSuspended());
|
||||
return topWin->LeaveModalState();
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_ASSERT(topWin->mModalStateDepth != 0);
|
||||
MOZ_ASSERT(mModalStateDepth != 0);
|
||||
MOZ_ASSERT(IsSuspended());
|
||||
MOZ_ASSERT(topWin->IsSuspended());
|
||||
topWin->mModalStateDepth--;
|
||||
mModalStateDepth--;
|
||||
|
||||
nsGlobalWindowInner* inner = topWin->GetCurrentInnerWindowInternal();
|
||||
|
||||
if (topWin->mModalStateDepth == 0) {
|
||||
nsGlobalWindowInner* inner = GetCurrentInnerWindowInternal();
|
||||
if (mModalStateDepth == 0) {
|
||||
if (inner) {
|
||||
inner->Resume();
|
||||
}
|
||||
|
||||
if (topWin->mSuspendedDoc) {
|
||||
nsCOMPtr<Document> currentDoc = topWin->GetExtantDoc();
|
||||
topWin->mSuspendedDoc->UnsuppressEventHandlingAndFireEvents(
|
||||
currentDoc == topWin->mSuspendedDoc);
|
||||
topWin->mSuspendedDoc = nullptr;
|
||||
if (mSuspendedDoc) {
|
||||
nsCOMPtr<Document> currentDoc = GetExtantDoc();
|
||||
mSuspendedDoc->UnsuppressEventHandlingAndFireEvents(currentDoc ==
|
||||
mSuspendedDoc);
|
||||
mSuspendedDoc = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@ -6424,12 +6442,12 @@ void nsGlobalWindowOuter::LeaveModalState() {
|
||||
inner->mLastDialogQuitTime = TimeStamp::Now();
|
||||
}
|
||||
|
||||
if (topWin->mModalStateDepth == 0) {
|
||||
if (mModalStateDepth == 0) {
|
||||
RefPtr<Event> event = NS_NewDOMEvent(inner, nullptr, nullptr);
|
||||
event->InitEvent(u"endmodalstate"_ns, true, false);
|
||||
event->SetTrusted(true);
|
||||
event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
|
||||
topWin->DispatchEvent(*event);
|
||||
DispatchEvent(*event);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -328,7 +328,7 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget,
|
||||
// Outer windows only.
|
||||
virtual void EnsureSizeAndPositionUpToDate() override;
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void EnterModalState() override;
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual nsGlobalWindowOuter* EnterModalState() override;
|
||||
virtual void LeaveModalState() override;
|
||||
|
||||
// Outer windows only.
|
||||
|
@ -913,8 +913,12 @@ class nsPIDOMWindowOuter : public mozIDOMWindowProxy {
|
||||
/**
|
||||
* Callback for notifying a window about a modal dialog being
|
||||
* opened/closed with the window as a parent.
|
||||
*
|
||||
* If any script can run between the enter and leave modal states, and the
|
||||
* window isn't top, the LeaveModalState() should be called on the window
|
||||
* returned by EnterModalState().
|
||||
*/
|
||||
virtual void EnterModalState() = 0;
|
||||
virtual nsPIDOMWindowOuter* EnterModalState() = 0;
|
||||
virtual void LeaveModalState() = 0;
|
||||
|
||||
virtual bool CanClose() = 0;
|
||||
|
@ -45,6 +45,7 @@ support-files =
|
||||
file_window_print_delayed_during_load.html
|
||||
file_window_print_sandboxed_iframe.html
|
||||
file_window_print_another_iframe_and_remove.html
|
||||
file_window_print_iframe_remove_on_afterprint.html
|
||||
file_coop_header2.html
|
||||
file_coop_header2.html^headers^
|
||||
|
||||
|
@ -212,6 +212,48 @@ add_task(async function test_window_print_coop_site() {
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_window_print_iframe_remove_on_afterprint() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["print.tab_modal.enabled", true]],
|
||||
});
|
||||
ok(
|
||||
!document.querySelector(".printPreviewBrowser"),
|
||||
"There shouldn't be any print preview browser"
|
||||
);
|
||||
await BrowserTestUtils.withNewTab(
|
||||
`${TEST_PATH}file_window_print_iframe_remove_on_afterprint.html`,
|
||||
async function(browser) {
|
||||
info("Waiting for dialog");
|
||||
await BrowserTestUtils.waitForCondition(
|
||||
() => !!document.querySelector(".printPreviewBrowser")
|
||||
);
|
||||
|
||||
let modalBefore = await SpecialPowers.spawn(browser, [], () => {
|
||||
return content.windowUtils.isInModalState();
|
||||
});
|
||||
|
||||
ok(modalBefore, "The tab should be in modal state");
|
||||
|
||||
// Clear the dialog.
|
||||
gBrowser.getTabDialogBox(browser).abortAllDialogs();
|
||||
|
||||
let [modalAfter, hasIframe] = await SpecialPowers.spawn(
|
||||
browser,
|
||||
[],
|
||||
() => {
|
||||
return [
|
||||
content.windowUtils.isInModalState(),
|
||||
!!content.document.querySelector("iframe"),
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
ok(!modalAfter, "Should've cleared the modal state properly");
|
||||
ok(!hasIframe, "Iframe should've been removed from the DOM");
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// FIXME(emilio): This test doesn't use window.print(), why is it on this file?
|
||||
add_task(async function test_focused_browsing_context() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
|
@ -0,0 +1,30 @@
|
||||
<!doctype html>
|
||||
<script>
|
||||
function closePrint() {
|
||||
document.documentElement.removeChild(this.__container__);
|
||||
}
|
||||
|
||||
function setPrint() {
|
||||
this.contentWindow.__container__ = this;
|
||||
this.contentWindow.onbeforeunload = closePrint;
|
||||
this.contentWindow.onafterprint = closePrint;
|
||||
this.contentWindow.print();
|
||||
}
|
||||
|
||||
function printPage(content) {
|
||||
var frame = document.createElement("iframe");
|
||||
frame.onload = setPrint;
|
||||
frame.style.position = "fixed";
|
||||
frame.style.right = "0";
|
||||
frame.style.bottom = "0";
|
||||
frame.style.width = "0";
|
||||
frame.style.height = "0";
|
||||
frame.style.border = "0";
|
||||
frame.srcdoc = content;
|
||||
document.documentElement.appendChild(frame);
|
||||
}
|
||||
|
||||
onload = function() {
|
||||
printPage("Something");
|
||||
}
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user