Source code

Revision control

Other Tools

1
/* vim: set ts=2 sw=2 et tw=80: */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
* License, v. 2.0. If a copy of the MPL was not distributed with this
4
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
"use strict";
7
8
var EXPORTED_SYMBOLS = ["PromptParent"];
9
10
ChromeUtils.defineModuleGetter(
11
this,
12
"PromptUtils",
14
);
15
ChromeUtils.defineModuleGetter(
16
this,
17
"Services",
19
);
20
21
/**
22
* @typedef {Object} Prompt
23
* @property {Function} resolver
24
* The resolve function to be called with the data from the Prompt
25
* after the user closes it.
26
* @property {Object} tabModalPrompt
27
* The TabModalPrompt being shown to the user.
28
*/
29
30
/**
31
* gBrowserPrompts weakly maps BrowsingContexts to a Map of their currently
32
* active Prompts.
33
*
34
* @type {WeakMap<BrowsingContext, Prompt>}
35
*/
36
let gBrowserPrompts = new WeakMap();
37
38
class PromptParent extends JSWindowActorParent {
39
didDestroy() {
40
// In the event that the subframe or tab crashed, make sure that
41
// we close any active Prompts.
42
this.forceClosePrompts(this.browsingContext);
43
}
44
45
/**
46
* Registers a new Prompt to be tracked for a particular BrowsingContext.
47
* We need to track a Prompt so that we can, for example, force-close the
48
* TabModalPrompt if the originating subframe or tab unloads or crashes.
49
*
50
* @param {BrowsingContext} browsingContext
51
* The BrowsingContext from which the request to open the Prompt came.
52
* @param {Object} tabModalPrompt
53
* The TabModalPrompt that will be shown to the user.
54
* @param {string} id
55
* A unique ID to differentiate multiple Prompts coming from the same
56
* BrowsingContext.
57
* @return {Promise}
58
* @resolves {Object}
59
* Resolves with the arguments returned from the TabModalPrompt when it
60
* is dismissed.
61
*/
62
registerPrompt(browsingContext, tabModalPrompt, id) {
63
let prompts = gBrowserPrompts.get(browsingContext);
64
if (!prompts) {
65
prompts = new Map();
66
gBrowserPrompts.set(browsingContext, prompts);
67
}
68
69
let promise = new Promise(resolve => {
70
prompts.set(id, {
71
tabModalPrompt,
72
resolver: resolve,
73
});
74
});
75
76
return promise;
77
}
78
79
/**
80
* Removes a Prompt for a BrowsingContext with a particular ID from the registry.
81
* This needs to be done to avoid leaking <xul:browser>'s.
82
*
83
* @param {BrowsingContext} browsingContext
84
* The BrowsingContext from which the request to open the Prompt came.
85
* @param {string} id
86
* A unique ID to differentiate multiple Prompts coming from the same
87
* BrowsingContext.
88
*/
89
unregisterPrompt(browsingContext, id) {
90
let prompts = gBrowserPrompts.get(browsingContext);
91
if (prompts) {
92
prompts.delete(id);
93
}
94
}
95
96
/**
97
* Programmatically closes a Prompt, without waiting for the TabModalPrompt to
98
* return with any arguments.
99
*
100
* @param {BrowsingContext} browsingContext
101
* The BrowsingContext from which the request to open the Prompt came.
102
* @param {string} id
103
* A unique ID to differentiate multiple Prompts coming from the same
104
* BrowsingContext.
105
*/
106
forceClosePrompt(browsingContext, id) {
107
let prompts = gBrowserPrompts.get(browsingContext);
108
let prompt = prompts.get(id);
109
if (prompt && prompt.tabModalPrompt) {
110
prompt.tabModalPrompt.abortPrompt();
111
}
112
}
113
114
/**
115
* Programmatically closes all Prompts for a BrowsingContext.
116
*
117
* @param {BrowsingContext} browsingContext
118
* The BrowsingContext from which the request to open the Prompts came.
119
*/
120
forceClosePrompts(browsingContext) {
121
let prompts = gBrowserPrompts.get(browsingContext) || [];
122
123
for (let prompt of prompts) {
124
if (prompt.tabModalPrompt) {
125
prompt.tabModalPrompt.abortPrompt();
126
}
127
}
128
}
129
130
receiveMessage(message) {
131
let browsingContext = this.browsingContext;
132
let args = message.data;
133
let id = args._remoteId;
134
135
switch (message.name) {
136
case "Prompt:Open": {
137
const COMMON_DIALOG = "chrome://global/content/commonDialog.xul";
138
const SELECT_DIALOG = "chrome://global/content/selectDialog.xul";
139
140
let topPrincipal =
141
browsingContext.top.currentWindowGlobal.documentPrincipal;
142
args.showAlertOrigin = topPrincipal.equals(args.promptPrincipal);
143
144
if (message.data.tabPrompt) {
145
return this.openTabPrompt(message.data, browsingContext, id);
146
}
147
let uri =
148
message.data.promptType == "select" ? SELECT_DIALOG : COMMON_DIALOG;
149
150
let browser = browsingContext.top.embedderElement;
151
return this.openModalWindow(uri, message.data, browser);
152
}
153
case "Prompt:ForceClose": {
154
this.forceClosePrompt(browsingContext, id);
155
break;
156
}
157
}
158
159
return undefined;
160
}
161
162
/**
163
* Opens a TabModalPrompt for a BrowsingContext, and puts the associated browser
164
* in the modal state until the TabModalPrompt is closed.
165
*
166
* @param {Object} args
167
* The arguments passed up from the BrowsingContext to be passed directly
168
* to the TabModalPrompt.
169
* @param {BrowsingContext} browsingContext
170
* The BrowsingContext from which the request to open the Prompts came.
171
* @param {string} id
172
* A unique ID to differentiate multiple Prompts coming from the same
173
* BrowsingContext.
174
* @return {Promise}
175
* @resolves {Object}
176
* Resolves with the arguments returned from the TabModalPrompt when it
177
* is dismissed.
178
*/
179
openTabPrompt(args, browsingContext, id) {
180
let browser = browsingContext.top.embedderElement;
181
let window = browser.ownerGlobal;
182
let tabPrompt = window.gBrowser.getTabModalPromptBox(browser);
183
let newPrompt;
184
let needRemove = false;
185
186
let onPromptClose = forceCleanup => {
187
let promptData = gBrowserPrompts.get(browsingContext);
188
if (!promptData || !promptData.has(id)) {
189
throw new Error(
190
"Failed to close a prompt since it wasn't registered for some reason."
191
);
192
}
193
194
let { resolver, tabModalPrompt } = promptData.get(id);
195
// It's possible that we removed the prompt during the
196
// appendPrompt call below. In that case, newPrompt will be
197
// undefined. We set the needRemove flag to remember to remove
198
// it right after we've finished adding it.
199
if (tabModalPrompt) {
200
tabPrompt.removePrompt(tabModalPrompt);
201
} else {
202
needRemove = true;
203
}
204
205
this.unregisterPrompt(browsingContext, id);
206
207
PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed", browser);
208
resolver(args);
209
browser.leaveModalState();
210
};
211
212
try {
213
browser.enterModalState();
214
let eventDetail = {
215
tabPrompt: true,
216
promptPrincipal: args.promptPrincipal,
217
inPermitUnload: args.inPermitUnload,
218
};
219
PromptUtils.fireDialogEvent(
220
window,
221
"DOMWillOpenModalDialog",
222
browser,
223
eventDetail
224
);
225
226
args.promptActive = true;
227
228
newPrompt = tabPrompt.appendPrompt(args, onPromptClose);
229
let promise = this.registerPrompt(browsingContext, newPrompt, id);
230
231
if (needRemove) {
232
tabPrompt.removePrompt(newPrompt);
233
}
234
235
return promise;
236
} catch (ex) {
237
Cu.reportError(ex);
238
onPromptClose(true);
239
}
240
241
return null;
242
}
243
244
/**
245
* Opens a window-modal prompt for a BrowsingContext, and puts the associated
246
* browser in the modal state until the prompt is closed.
247
*
248
* @param {string} uri
249
* The URI to a XUL document to be loaded in a modal window.
250
* @param {Object} args
251
* The arguments passed up from the BrowsingContext to be passed
252
* directly to the modal window.
253
* @param {Element} browser
254
* The <xul:browser> from which the request to open the window-modal
255
* prompt came.
256
* @return {Promise}
257
* @resolves {Object}
258
* Resolves with the arguments returned from the window-modal
259
* prompt when it is dismissed.
260
*/
261
openModalWindow(uri, args, browser) {
262
let window = browser.ownerGlobal;
263
try {
264
browser.enterModalState();
265
PromptUtils.fireDialogEvent(window, "DOMWillOpenModalDialog", browser);
266
let bag = PromptUtils.objectToPropBag(args);
267
268
Services.ww.openWindow(
269
window,
270
uri,
271
"_blank",
272
"centerscreen,chrome,modal,titlebar",
273
bag
274
);
275
276
PromptUtils.propBagToObject(bag, args);
277
} finally {
278
browser.leaveModalState();
279
PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed", browser);
280
}
281
return Promise.resolve(args);
282
}
283
}