dom/browser-element/BrowserElementPromptService.jsm

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

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

mercurial