|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 /* vim: set ft=javascript : */ |
|
5 |
|
6 "use strict"; |
|
7 |
|
8 let Cu = Components.utils; |
|
9 let Ci = Components.interfaces; |
|
10 let Cc = Components.classes; |
|
11 let Cr = Components.results; |
|
12 let Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); |
|
13 |
|
14 this.EXPORTED_SYMBOLS = ["BrowserElementPromptService"]; |
|
15 |
|
16 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
17 Cu.import("resource://gre/modules/Services.jsm"); |
|
18 |
|
19 const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; |
|
20 const BROWSER_FRAMES_ENABLED_PREF = "dom.mozBrowserFramesEnabled"; |
|
21 |
|
22 function debug(msg) { |
|
23 //dump("BrowserElementPromptService - " + msg + "\n"); |
|
24 } |
|
25 |
|
26 function BrowserElementPrompt(win, browserElementChild) { |
|
27 this._win = win; |
|
28 this._browserElementChild = browserElementChild; |
|
29 } |
|
30 |
|
31 BrowserElementPrompt.prototype = { |
|
32 QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]), |
|
33 |
|
34 alert: function(title, text) { |
|
35 this._browserElementChild.showModalPrompt( |
|
36 this._win, {promptType: "alert", title: title, message: text, returnValue: undefined}); |
|
37 }, |
|
38 |
|
39 alertCheck: function(title, text, checkMsg, checkState) { |
|
40 // Treat this like a normal alert() call, ignoring the checkState. The |
|
41 // front-end can do its own suppression of the alert() if it wants. |
|
42 this.alert(title, text); |
|
43 }, |
|
44 |
|
45 confirm: function(title, text) { |
|
46 return this._browserElementChild.showModalPrompt( |
|
47 this._win, {promptType: "confirm", title: title, message: text, returnValue: undefined}); |
|
48 }, |
|
49 |
|
50 confirmCheck: function(title, text, checkMsg, checkState) { |
|
51 return this.confirm(title, text); |
|
52 }, |
|
53 |
|
54 // Each button is described by an object with the following schema |
|
55 // { |
|
56 // string messageType, // 'builtin' or 'custom' |
|
57 // string message, // 'ok', 'cancel', 'yes', 'no', 'save', 'dontsave', |
|
58 // // 'revert' or a string from caller if messageType was 'custom'. |
|
59 // } |
|
60 // |
|
61 // Expected result from embedder: |
|
62 // { |
|
63 // int button, // Index of the button that user pressed. |
|
64 // boolean checked, // True if the check box is checked. |
|
65 // } |
|
66 confirmEx: function(title, text, buttonFlags, button0Title, button1Title, |
|
67 button2Title, checkMsg, checkState) { |
|
68 let buttonProperties = this._buildConfirmExButtonProperties(buttonFlags, |
|
69 button0Title, |
|
70 button1Title, |
|
71 button2Title); |
|
72 let defaultReturnValue = { selectedButton: buttonProperties.defaultButton }; |
|
73 if (checkMsg) { |
|
74 defaultReturnValue.checked = checkState.value; |
|
75 } |
|
76 let ret = this._browserElementChild.showModalPrompt( |
|
77 this._win, |
|
78 { |
|
79 promptType: "custom-prompt", |
|
80 title: title, |
|
81 message: text, |
|
82 defaultButton: buttonProperties.defaultButton, |
|
83 buttons: buttonProperties.buttons, |
|
84 showCheckbox: !!checkMsg, |
|
85 checkboxMessage: checkMsg, |
|
86 checkboxCheckedByDefault: !!checkState.value, |
|
87 returnValue: defaultReturnValue |
|
88 } |
|
89 ); |
|
90 if (checkMsg) { |
|
91 checkState.value = ret.checked; |
|
92 } |
|
93 return buttonProperties.indexToButtonNumberMap[ret.selectedButton]; |
|
94 }, |
|
95 |
|
96 prompt: function(title, text, value, checkMsg, checkState) { |
|
97 let rv = this._browserElementChild.showModalPrompt( |
|
98 this._win, |
|
99 { promptType: "prompt", |
|
100 title: title, |
|
101 message: text, |
|
102 initialValue: value.value, |
|
103 returnValue: null }); |
|
104 |
|
105 value.value = rv; |
|
106 |
|
107 // nsIPrompt::Prompt returns true if the user pressed "OK" at the prompt, |
|
108 // and false if the user pressed "Cancel". |
|
109 // |
|
110 // BrowserElementChild returns null for "Cancel" and returns the string the |
|
111 // user entered otherwise. |
|
112 return rv !== null; |
|
113 }, |
|
114 |
|
115 promptUsernameAndPassword: function(title, text, username, password, checkMsg, checkState) { |
|
116 throw Cr.NS_ERROR_NOT_IMPLEMENTED; |
|
117 }, |
|
118 |
|
119 promptPassword: function(title, text, password, checkMsg, checkState) { |
|
120 throw Cr.NS_ERROR_NOT_IMPLEMENTED; |
|
121 }, |
|
122 |
|
123 select: function(title, text, aCount, aSelectList, aOutSelection) { |
|
124 throw Cr.NS_ERROR_NOT_IMPLEMENTED; |
|
125 }, |
|
126 |
|
127 _buildConfirmExButtonProperties: function(buttonFlags, button0Title, |
|
128 button1Title, button2Title) { |
|
129 let r = { |
|
130 defaultButton: -1, |
|
131 buttons: [], |
|
132 // This map is for translating array index to the button number that |
|
133 // is recognized by Gecko. This shouldn't be exposed to embedder. |
|
134 indexToButtonNumberMap: [] |
|
135 }; |
|
136 |
|
137 let defaultButton = 0; // Default to Button 0. |
|
138 if (buttonFlags & Ci.nsIPrompt.BUTTON_POS_1_DEFAULT) { |
|
139 defaultButton = 1; |
|
140 } else if (buttonFlags & Ci.nsIPrompt.BUTTON_POS_2_DEFAULT) { |
|
141 defaultButton = 2; |
|
142 } |
|
143 |
|
144 // Properties of each button. |
|
145 let buttonPositions = [ |
|
146 Ci.nsIPrompt.BUTTON_POS_0, |
|
147 Ci.nsIPrompt.BUTTON_POS_1, |
|
148 Ci.nsIPrompt.BUTTON_POS_2 |
|
149 ]; |
|
150 |
|
151 function buildButton(buttonTitle, buttonNumber) { |
|
152 let ret = {}; |
|
153 let buttonPosition = buttonPositions[buttonNumber]; |
|
154 let mask = 0xff * buttonPosition; // 8 bit mask |
|
155 let titleType = (buttonFlags & mask) / buttonPosition; |
|
156 |
|
157 ret.messageType = 'builtin'; |
|
158 switch(titleType) { |
|
159 case Ci.nsIPrompt.BUTTON_TITLE_OK: |
|
160 ret.message = 'ok'; |
|
161 break; |
|
162 case Ci.nsIPrompt.BUTTON_TITLE_CANCEL: |
|
163 ret.message = 'cancel'; |
|
164 break; |
|
165 case Ci.nsIPrompt.BUTTON_TITLE_YES: |
|
166 ret.message = 'yes'; |
|
167 break; |
|
168 case Ci.nsIPrompt.BUTTON_TITLE_NO: |
|
169 ret.message = 'no'; |
|
170 break; |
|
171 case Ci.nsIPrompt.BUTTON_TITLE_SAVE: |
|
172 ret.message = 'save'; |
|
173 break; |
|
174 case Ci.nsIPrompt.BUTTON_TITLE_DONT_SAVE: |
|
175 ret.message = 'dontsave'; |
|
176 break; |
|
177 case Ci.nsIPrompt.BUTTON_TITLE_REVERT: |
|
178 ret.message = 'revert'; |
|
179 break; |
|
180 case Ci.nsIPrompt.BUTTON_TITLE_IS_STRING: |
|
181 ret.message = buttonTitle; |
|
182 ret.messageType = 'custom'; |
|
183 break; |
|
184 default: |
|
185 // This button is not shown. |
|
186 return; |
|
187 } |
|
188 |
|
189 // If this is the default button, set r.defaultButton to |
|
190 // the index of this button in the array. This value is going to be |
|
191 // exposed to the embedder. |
|
192 if (defaultButton === buttonNumber) { |
|
193 r.defaultButton = r.buttons.length; |
|
194 } |
|
195 r.buttons.push(ret); |
|
196 r.indexToButtonNumberMap.push(buttonNumber); |
|
197 } |
|
198 |
|
199 buildButton(button0Title, 0); |
|
200 buildButton(button1Title, 1); |
|
201 buildButton(button2Title, 2); |
|
202 |
|
203 // If defaultButton is still -1 here, it means the default button won't |
|
204 // be shown. |
|
205 if (r.defaultButton === -1) { |
|
206 throw new Components.Exception("Default button won't be shown", |
|
207 Cr.NS_ERROR_FAILURE); |
|
208 } |
|
209 |
|
210 return r; |
|
211 }, |
|
212 }; |
|
213 |
|
214 |
|
215 function BrowserElementAuthPrompt() { |
|
216 } |
|
217 |
|
218 BrowserElementAuthPrompt.prototype = { |
|
219 QueryInterface: XPCOMUtils.generateQI([Ci.nsIAuthPrompt2]), |
|
220 |
|
221 promptAuth: function promptAuth(channel, level, authInfo) { |
|
222 throw Cr.NS_ERROR_NOT_IMPLEMENTED; |
|
223 }, |
|
224 |
|
225 asyncPromptAuth: function asyncPromptAuth(channel, callback, context, level, authInfo) { |
|
226 debug("asyncPromptAuth"); |
|
227 |
|
228 // The cases that we don't support now. |
|
229 if ((authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) && |
|
230 (authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD)) { |
|
231 throw Cr.NS_ERROR_FAILURE; |
|
232 } |
|
233 |
|
234 let frame = this._getFrameFromChannel(channel); |
|
235 if (!frame) { |
|
236 debug("Cannot get frame, asyncPromptAuth fail"); |
|
237 throw Cr.NS_ERROR_FAILURE; |
|
238 } |
|
239 |
|
240 let browserElementParent = |
|
241 BrowserElementPromptService.getBrowserElementParentForFrame(frame); |
|
242 |
|
243 if (!browserElementParent) { |
|
244 debug("Failed to load browser element parent."); |
|
245 throw Cr.NS_ERROR_FAILURE; |
|
246 } |
|
247 |
|
248 let consumer = { |
|
249 QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), |
|
250 callback: callback, |
|
251 context: context, |
|
252 cancel: function() { |
|
253 this.callback.onAuthCancelled(this.context, false); |
|
254 this.callback = null; |
|
255 this.context = null; |
|
256 } |
|
257 }; |
|
258 |
|
259 let [hostname, httpRealm] = this._getAuthTarget(channel, authInfo); |
|
260 let hashKey = level + "|" + hostname + "|" + httpRealm; |
|
261 let asyncPrompt = this._asyncPrompts[hashKey]; |
|
262 if (asyncPrompt) { |
|
263 asyncPrompt.consumers.push(consumer); |
|
264 return consumer; |
|
265 } |
|
266 |
|
267 asyncPrompt = { |
|
268 consumers: [consumer], |
|
269 channel: channel, |
|
270 authInfo: authInfo, |
|
271 level: level, |
|
272 inProgress: false, |
|
273 browserElementParent: browserElementParent |
|
274 }; |
|
275 |
|
276 this._asyncPrompts[hashKey] = asyncPrompt; |
|
277 this._doAsyncPrompt(); |
|
278 return consumer; |
|
279 }, |
|
280 |
|
281 // Utilities for nsIAuthPrompt2 ---------------- |
|
282 |
|
283 _asyncPrompts: {}, |
|
284 _asyncPromptInProgress: new WeakMap(), |
|
285 _doAsyncPrompt: function() { |
|
286 // Find the key of a prompt whose browser element parent does not have |
|
287 // async prompt in progress. |
|
288 let hashKey = null; |
|
289 for (let key in this._asyncPrompts) { |
|
290 let prompt = this._asyncPrompts[key]; |
|
291 if (!this._asyncPromptInProgress.get(prompt.browserElementParent)) { |
|
292 hashKey = key; |
|
293 break; |
|
294 } |
|
295 } |
|
296 |
|
297 // Didn't find an available prompt, so just return. |
|
298 if (!hashKey) |
|
299 return; |
|
300 |
|
301 let prompt = this._asyncPrompts[hashKey]; |
|
302 let [hostname, httpRealm] = this._getAuthTarget(prompt.channel, |
|
303 prompt.authInfo); |
|
304 |
|
305 this._asyncPromptInProgress.set(prompt.browserElementParent, true); |
|
306 prompt.inProgress = true; |
|
307 |
|
308 let self = this; |
|
309 let callback = function(ok, username, password) { |
|
310 debug("Async auth callback is called, ok = " + |
|
311 ok + ", username = " + username); |
|
312 |
|
313 // Here we got the username and password provided by embedder, or |
|
314 // ok = false if the prompt was cancelled by embedder. |
|
315 delete self._asyncPrompts[hashKey]; |
|
316 prompt.inProgress = false; |
|
317 self._asyncPromptInProgress.delete(prompt.browserElementParent); |
|
318 |
|
319 // Fill authentication information with username and password provided |
|
320 // by user. |
|
321 let flags = prompt.authInfo.flags; |
|
322 if (username) { |
|
323 if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) { |
|
324 // Domain is separated from username by a backslash |
|
325 let idx = username.indexOf("\\"); |
|
326 if (idx == -1) { |
|
327 prompt.authInfo.username = username; |
|
328 } else { |
|
329 prompt.authInfo.domain = username.substring(0, idx); |
|
330 prompt.authInfo.username = username.substring(idx + 1); |
|
331 } |
|
332 } else { |
|
333 prompt.authInfo.username = username; |
|
334 } |
|
335 } |
|
336 |
|
337 if (password) { |
|
338 prompt.authInfo.password = password; |
|
339 } |
|
340 |
|
341 for each (let consumer in prompt.consumers) { |
|
342 if (!consumer.callback) { |
|
343 // Not having a callback means that consumer didn't provide it |
|
344 // or canceled the notification. |
|
345 continue; |
|
346 } |
|
347 |
|
348 try { |
|
349 if (ok) { |
|
350 debug("Ok, calling onAuthAvailable to finish auth"); |
|
351 consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo); |
|
352 } else { |
|
353 debug("Cancelled, calling onAuthCancelled to finish auth."); |
|
354 consumer.callback.onAuthCancelled(consumer.context, true); |
|
355 } |
|
356 } catch (e) { /* Throw away exceptions caused by callback */ } |
|
357 } |
|
358 |
|
359 // Process the next prompt, if one is pending. |
|
360 self._doAsyncPrompt(); |
|
361 }; |
|
362 |
|
363 let runnable = { |
|
364 run: function() { |
|
365 // Call promptAuth of browserElementParent, to show the prompt. |
|
366 prompt.browserElementParent.promptAuth( |
|
367 self._createAuthDetail(prompt.channel, prompt.authInfo), |
|
368 callback); |
|
369 } |
|
370 } |
|
371 |
|
372 Services.tm.currentThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL); |
|
373 }, |
|
374 |
|
375 _getFrameFromChannel: function(channel) { |
|
376 let loadContext = channel.notificationCallbacks.getInterface(Ci.nsILoadContext); |
|
377 return loadContext.topFrameElement; |
|
378 }, |
|
379 |
|
380 _createAuthDetail: function(channel, authInfo) { |
|
381 let [hostname, httpRealm] = this._getAuthTarget(channel, authInfo); |
|
382 return { |
|
383 host: hostname, |
|
384 realm: httpRealm, |
|
385 username: authInfo.username, |
|
386 isOnlyPassword: !!(authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD) |
|
387 }; |
|
388 }, |
|
389 |
|
390 _getAuthTarget : function (channel, authInfo) { |
|
391 let hostname = this._getFormattedHostname(channel.URI); |
|
392 |
|
393 // If a HTTP WWW-Authenticate header specified a realm, that value |
|
394 // will be available here. If it wasn't set or wasn't HTTP, we'll use |
|
395 // the formatted hostname instead. |
|
396 let realm = authInfo.realm; |
|
397 if (!realm) |
|
398 realm = hostname; |
|
399 |
|
400 return [hostname, realm]; |
|
401 }, |
|
402 |
|
403 _getFormattedHostname : function(uri) { |
|
404 let scheme = uri.scheme; |
|
405 let hostname = scheme + "://" + uri.host; |
|
406 |
|
407 // If the URI explicitly specified a port, only include it when |
|
408 // it's not the default. (We never want "http://foo.com:80") |
|
409 let port = uri.port; |
|
410 if (port != -1) { |
|
411 let handler = Services.io.getProtocolHandler(scheme); |
|
412 if (port != handler.defaultPort) |
|
413 hostname += ":" + port; |
|
414 } |
|
415 return hostname; |
|
416 } |
|
417 }; |
|
418 |
|
419 |
|
420 function AuthPromptWrapper(oldImpl, browserElementImpl) { |
|
421 this._oldImpl = oldImpl; |
|
422 this._browserElementImpl = browserElementImpl; |
|
423 } |
|
424 |
|
425 AuthPromptWrapper.prototype = { |
|
426 QueryInterface: XPCOMUtils.generateQI([Ci.nsIAuthPrompt2]), |
|
427 promptAuth: function(channel, level, authInfo) { |
|
428 if (this._canGetParentElement(channel)) { |
|
429 return this._browserElementImpl.promptAuth(channel, level, authInfo); |
|
430 } else { |
|
431 return this._oldImpl.promptAuth(channel, level, authInfo); |
|
432 } |
|
433 }, |
|
434 |
|
435 asyncPromptAuth: function(channel, callback, context, level, authInfo) { |
|
436 if (this._canGetParentElement(channel)) { |
|
437 return this._browserElementImpl.asyncPromptAuth(channel, callback, context, level, authInfo); |
|
438 } else { |
|
439 return this._oldImpl.asyncPromptAuth(channel, callback, context, level, authInfo); |
|
440 } |
|
441 }, |
|
442 |
|
443 _canGetParentElement: function(channel) { |
|
444 try { |
|
445 let frame = channel.notificationCallbacks.getInterface(Ci.nsILoadContext).topFrameElement; |
|
446 if (!frame) |
|
447 return false; |
|
448 |
|
449 if (!BrowserElementPromptService.getBrowserElementParentForFrame(frame)) |
|
450 return false; |
|
451 |
|
452 return true; |
|
453 } catch (e) { |
|
454 return false; |
|
455 } |
|
456 } |
|
457 }; |
|
458 |
|
459 function BrowserElementPromptFactory(toWrap) { |
|
460 this._wrapped = toWrap; |
|
461 } |
|
462 |
|
463 BrowserElementPromptFactory.prototype = { |
|
464 classID: Components.ID("{24f3d0cf-e417-4b85-9017-c9ecf8bb1299}"), |
|
465 QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptFactory]), |
|
466 |
|
467 _mayUseNativePrompt: function() { |
|
468 try { |
|
469 return Services.prefs.getBoolPref("browser.prompt.allowNative"); |
|
470 } catch (e) { |
|
471 // This properity is default to true. |
|
472 return true; |
|
473 } |
|
474 }, |
|
475 |
|
476 _getNativePromptIfAllowed: function(win, iid, err) { |
|
477 if (this._mayUseNativePrompt()) |
|
478 return this._wrapped.getPrompt(win, iid); |
|
479 else { |
|
480 // Not allowed, throw an exception. |
|
481 throw err; |
|
482 } |
|
483 }, |
|
484 |
|
485 getPrompt: function(win, iid) { |
|
486 // It is possible for some object to get a prompt without passing |
|
487 // valid reference of window, like nsNSSComponent. In such case, we |
|
488 // should just fall back to the native prompt service |
|
489 if (!win) |
|
490 return this._getNativePromptIfAllowed(win, iid, Cr.NS_ERROR_INVALID_ARG); |
|
491 |
|
492 if (iid.number != Ci.nsIPrompt.number && |
|
493 iid.number != Ci.nsIAuthPrompt2.number) { |
|
494 debug("We don't recognize the requested IID (" + iid + ", " + |
|
495 "allowed IID: " + |
|
496 "nsIPrompt=" + Ci.nsIPrompt + ", " + |
|
497 "nsIAuthPrompt2=" + Ci.nsIAuthPrompt2 + ")"); |
|
498 return this._getNativePromptIfAllowed(win, iid, Cr.NS_ERROR_INVALID_ARG); |
|
499 } |
|
500 |
|
501 // Try to find a BrowserElementChild for the window. |
|
502 let browserElementChild = |
|
503 BrowserElementPromptService.getBrowserElementChildForWindow(win); |
|
504 |
|
505 if (iid.number === Ci.nsIAuthPrompt2.number) { |
|
506 debug("Caller requests an instance of nsIAuthPrompt2."); |
|
507 |
|
508 if (browserElementChild) { |
|
509 // If we are able to get a BrowserElementChild, it means that |
|
510 // the auth prompt is for a mozbrowser. Therefore we don't need to |
|
511 // fall back. |
|
512 return new BrowserElementAuthPrompt().QueryInterface(iid); |
|
513 } |
|
514 |
|
515 // Because nsIAuthPrompt2 is called in parent process. If caller |
|
516 // wants nsIAuthPrompt2 and we cannot get BrowserElementchild, |
|
517 // it doesn't mean that we should fallback. It is possible that we can |
|
518 // get the BrowserElementParent from nsIChannel that passed to |
|
519 // functions of nsIAuthPrompt2. |
|
520 if (this._mayUseNativePrompt()) { |
|
521 return new AuthPromptWrapper( |
|
522 this._wrapped.getPrompt(win, iid), |
|
523 new BrowserElementAuthPrompt().QueryInterface(iid)) |
|
524 .QueryInterface(iid); |
|
525 } else { |
|
526 // Falling back is not allowed, so we don't need wrap the |
|
527 // BrowserElementPrompt. |
|
528 return new BrowserElementAuthPrompt().QueryInterface(iid); |
|
529 } |
|
530 } |
|
531 |
|
532 if (!browserElementChild) { |
|
533 debug("We can't find a browserElementChild for " + |
|
534 win + ", " + win.location); |
|
535 return this._getNativePromptIfAllowed(win, iid, Cr.NS_ERROR_FAILURE); |
|
536 } |
|
537 |
|
538 debug("Returning wrapped getPrompt for " + win); |
|
539 return new BrowserElementPrompt(win, browserElementChild) |
|
540 .QueryInterface(iid); |
|
541 } |
|
542 }; |
|
543 |
|
544 this.BrowserElementPromptService = { |
|
545 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, |
|
546 Ci.nsISupportsWeakReference]), |
|
547 |
|
548 _initialized: false, |
|
549 |
|
550 _init: function() { |
|
551 if (this._initialized) { |
|
552 return; |
|
553 } |
|
554 |
|
555 // If the pref is disabled, do nothing except wait for the pref to change. |
|
556 if (!this._browserFramesPrefEnabled()) { |
|
557 var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); |
|
558 prefs.addObserver(BROWSER_FRAMES_ENABLED_PREF, this, /* ownsWeak = */ true); |
|
559 return; |
|
560 } |
|
561 |
|
562 this._initialized = true; |
|
563 this._browserElementParentMap = new WeakMap(); |
|
564 |
|
565 var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); |
|
566 os.addObserver(this, "outer-window-destroyed", /* ownsWeak = */ true); |
|
567 |
|
568 // Wrap the existing @mozilla.org/prompter;1 implementation. |
|
569 var contractID = "@mozilla.org/prompter;1"; |
|
570 var oldCID = Cm.contractIDToCID(contractID); |
|
571 var newCID = BrowserElementPromptFactory.prototype.classID; |
|
572 var oldFactory = Cm.getClassObject(Cc[contractID], Ci.nsIFactory); |
|
573 |
|
574 if (oldCID == newCID) { |
|
575 debug("WARNING: Wrapped prompt factory is already installed!"); |
|
576 return; |
|
577 } |
|
578 |
|
579 Cm.unregisterFactory(oldCID, oldFactory); |
|
580 |
|
581 var oldInstance = oldFactory.createInstance(null, Ci.nsIPromptFactory); |
|
582 var newInstance = new BrowserElementPromptFactory(oldInstance); |
|
583 |
|
584 var newFactory = { |
|
585 createInstance: function(outer, iid) { |
|
586 if (outer != null) { |
|
587 throw Cr.NS_ERROR_NO_AGGREGATION; |
|
588 } |
|
589 return newInstance.QueryInterface(iid); |
|
590 } |
|
591 }; |
|
592 Cm.registerFactory(newCID, |
|
593 "BrowserElementPromptService's prompter;1 wrapper", |
|
594 contractID, newFactory); |
|
595 |
|
596 debug("Done installing new prompt factory."); |
|
597 }, |
|
598 |
|
599 _getOuterWindowID: function(win) { |
|
600 return win.QueryInterface(Ci.nsIInterfaceRequestor) |
|
601 .getInterface(Ci.nsIDOMWindowUtils) |
|
602 .outerWindowID; |
|
603 }, |
|
604 |
|
605 _browserElementChildMap: {}, |
|
606 mapWindowToBrowserElementChild: function(win, browserElementChild) { |
|
607 this._browserElementChildMap[this._getOuterWindowID(win)] = browserElementChild; |
|
608 }, |
|
609 |
|
610 getBrowserElementChildForWindow: function(win) { |
|
611 // We only have a mapping for <iframe mozbrowser>s, not their inner |
|
612 // <iframes>, so we look up win.top below. window.top (when called from |
|
613 // script) respects <iframe mozbrowser> boundaries. |
|
614 return this._browserElementChildMap[this._getOuterWindowID(win.top)]; |
|
615 }, |
|
616 |
|
617 mapFrameToBrowserElementParent: function(frame, browserElementParent) { |
|
618 this._browserElementParentMap.set(frame, browserElementParent); |
|
619 }, |
|
620 |
|
621 getBrowserElementParentForFrame: function(frame) { |
|
622 return this._browserElementParentMap.get(frame); |
|
623 }, |
|
624 |
|
625 _observeOuterWindowDestroyed: function(outerWindowID) { |
|
626 let id = outerWindowID.QueryInterface(Ci.nsISupportsPRUint64).data; |
|
627 debug("observeOuterWindowDestroyed " + id); |
|
628 delete this._browserElementChildMap[outerWindowID.data]; |
|
629 }, |
|
630 |
|
631 _browserFramesPrefEnabled: function() { |
|
632 var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); |
|
633 try { |
|
634 return prefs.getBoolPref(BROWSER_FRAMES_ENABLED_PREF); |
|
635 } |
|
636 catch(e) { |
|
637 return false; |
|
638 } |
|
639 }, |
|
640 |
|
641 observe: function(subject, topic, data) { |
|
642 switch(topic) { |
|
643 case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID: |
|
644 if (data == BROWSER_FRAMES_ENABLED_PREF) { |
|
645 this._init(); |
|
646 } |
|
647 break; |
|
648 case "outer-window-destroyed": |
|
649 this._observeOuterWindowDestroyed(subject); |
|
650 break; |
|
651 default: |
|
652 debug("Observed unexpected topic " + topic); |
|
653 } |
|
654 } |
|
655 }; |
|
656 |
|
657 BrowserElementPromptService._init(); |