Skip to content

Commit 2d863e9

Browse files
committed
Bug 1655866: Part 1 - Add async version of beforeUnloadCheck prompt. r=geckoview-reviewers,Gijs,agi
Differential Revision: https://phabricator.services.mozilla.com/D88314
1 parent 3e0a30e commit 2d863e9

File tree

6 files changed

+85
-26
lines changed

6 files changed

+85
-26
lines changed

browser/components/prompts/PromptCollection.jsm

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class PromptCollection {
7171
return buttonPressed === 0;
7272
}
7373

74-
beforeUnloadCheck(browsingContext) {
74+
beforeUnloadCheckInternal(browsingContext, sync) {
7575
let title;
7676
let message;
7777
let leaveLabel;
@@ -94,9 +94,17 @@ class PromptCollection {
9494
}
9595

9696
let contentViewer = browsingContext?.docShell?.contentViewer;
97-
let modalType = contentViewer?.isTabModalPromptAllowed
98-
? Ci.nsIPromptService.MODAL_TYPE_CONTENT
99-
: Ci.nsIPromptService.MODAL_TYPE_WINDOW;
97+
98+
// TODO: Do we really want to allow modal dialogs from inactive
99+
// content viewers at all, particularly for permit unload prompts?
100+
let modalAllowed = contentViewer
101+
? contentViewer.isTabModalPromptAllowed
102+
: browsingContext.ancestorsAreCurrent;
103+
104+
let modalType =
105+
Ci.nsIPromptService[
106+
modalAllowed ? "MODAL_TYPE_CONTENT" : "MODAL_TYPE_WINDOW"
107+
];
100108

101109
let buttonFlags =
102110
Ci.nsIPromptService.BUTTON_POS_0_DEFAULT |
@@ -105,20 +113,55 @@ class PromptCollection {
105113
(Ci.nsIPromptService.BUTTON_TITLE_IS_STRING *
106114
Ci.nsIPromptService.BUTTON_POS_1);
107115

108-
let buttonPressed = Services.prompt.confirmExBC(
109-
browsingContext,
110-
modalType,
111-
title,
112-
message,
113-
buttonFlags,
114-
leaveLabel,
115-
stayLabel,
116-
null,
117-
null,
118-
{}
119-
);
116+
if (sync) {
117+
let buttonNumClicked = Services.prompt.confirmExBC(
118+
browsingContext,
119+
modalType,
120+
title,
121+
message,
122+
buttonFlags,
123+
leaveLabel,
124+
stayLabel,
125+
null,
126+
null,
127+
{}
128+
);
120129

121-
return buttonPressed === 0;
130+
return buttonNumClicked === 0;
131+
}
132+
133+
return Services.prompt
134+
.asyncConfirmEx(
135+
browsingContext,
136+
modalType,
137+
title,
138+
message,
139+
buttonFlags,
140+
leaveLabel,
141+
stayLabel,
142+
null,
143+
null,
144+
false,
145+
// Tell the prompt service that this is a permit unload prompt
146+
// so that it can set the appropriate flag on the detail object
147+
// of the events it dispatches. This happens automatically for
148+
// the sync version of the prompt, which is always dispatched
149+
// from the content process, where the flag comes from the
150+
// content viewer which triggers the prompt.
151+
{ inPermitUnload: true }
152+
)
153+
.then(
154+
result =>
155+
result.QueryInterface(Ci.nsIPropertyBag2).get("buttonNumClicked") == 0
156+
);
157+
}
158+
159+
beforeUnloadCheck(browsingContext) {
160+
return this.beforeUnloadCheckInternal(browsingContext, /* sync */ true);
161+
}
162+
163+
asyncBeforeUnloadCheck(browsingContext) {
164+
return this.beforeUnloadCheckInternal(browsingContext, /* sync */ false);
122165
}
123166
}
124167

@@ -144,9 +187,6 @@ for (const [bundleName, bundleUrl] of Object.entries(BUNDLES)) {
144187
);
145188
}
146189

147-
PromptCollection.prototype.classID = Components.ID(
148-
"{7913837c-9623-11ea-bb37-0242ac130002}"
149-
);
150190
PromptCollection.prototype.QueryInterface = ChromeUtils.generateQI([
151191
"nsIPromptCollection",
152192
]);

dom/chrome-webidl/BrowsingContext.webidl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ interface BrowsingContext {
6868

6969
readonly attribute WindowContext? topWindowContext;
7070

71+
readonly attribute boolean ancestorsAreCurrent;
72+
7173
[SetterThrows] attribute [TreatNullAs=EmptyString] DOMString customPlatform;
7274

7375
[SetterThrows] attribute [TreatNullAs=EmptyString] DOMString customUserAgent;

mobile/android/components/geckoview/PromptCollection.jsm

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,17 @@ class PromptCollection {
3737
const result = prompter.showPrompt(msg);
3838
return !!result?.allow;
3939
}
40-
}
4140

42-
PromptCollection.prototype.classID = Components.ID(
43-
"{3e30d2a0-9934-11ea-bb37-0242ac130002}"
44-
);
41+
asyncBeforeUnloadCheck(browsingContext) {
42+
return new Promise(resolve => {
43+
const msg = {
44+
type: "beforeUnload",
45+
};
46+
const prompter = new GeckoViewPrompter(browsingContext);
47+
prompter.asyncShowPrompt(msg, resolve);
48+
}).then(result => !!result?.allow);
49+
}
50+
}
4551

4652
PromptCollection.prototype.QueryInterface = ChromeUtils.generateQI([
4753
"nsIPromptCollection",

toolkit/components/prompts/src/Prompter.jsm

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ Prompter.prototype = {
346346
* @param {String} checkLabel - Text to appear with the checkbox.
347347
* Null if no checkbox.
348348
* @param {Boolean} checkValue - The initial checked state of the checkbox.
349+
* @param {Object} [extraArgs] - Extra arguments for the prompt metadata.
349350
* @returns {Promise<nsIPropertyBag<{ buttonNumClicked: Number, checked: Boolean }>>}
350351
*/
351352
asyncConfirmEx(browsingContext, modalType, ...promptArgs) {
@@ -1400,7 +1401,8 @@ class ModalPrompter {
14001401
button1,
14011402
button2,
14021403
checkLabel,
1403-
checkValue
1404+
checkValue,
1405+
extraArgs = {}
14041406
) {
14051407
if (!title) {
14061408
title = PromptUtils.getLocalizedString("Confirm");
@@ -1414,6 +1416,7 @@ class ModalPrompter {
14141416
checked: this.async ? checkValue : checkValue.value,
14151417
ok: false,
14161418
buttonNumClicked: 1,
1419+
...extraArgs,
14171420
};
14181421

14191422
let [

toolkit/components/windowwatcher/nsIPromptCollection.idl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ interface nsIPromptCollection : nsISupports
2323
*/
2424
boolean beforeUnloadCheck(in BrowsingContext aBrowsingContext);
2525

26+
/**
27+
* Like `beforeUnloadCheck`, but does not spin a nested event loop, and
28+
* instead returns a promise which resolves to true if navigation should be
29+
* allowed, and false if not.
30+
*/
31+
Promise asyncBeforeUnloadCheck(in BrowsingContext aBrowsingContext);
32+
2633
/**
2734
* Puts up a dialog for the confirm repost prompt.
2835
*

toolkit/components/windowwatcher/nsIPromptService.idl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,8 @@ interface nsIPromptService : nsISupports
364364
in wstring aButton1Title,
365365
in wstring aButton2Title,
366366
in wstring aCheckMsg,
367-
in boolean aCheckState);
367+
in boolean aCheckState,
368+
[optional] in jsval aExtraArgs);
368369
/**
369370
* Puts up a dialog with an edit field and an optional, labeled checkbox.
370371
*

0 commit comments

Comments
 (0)