dom/browser-element/BrowserElementPromptService.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/browser-element/BrowserElementPromptService.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,657 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +/* vim: set ft=javascript : */
     1.8 +
     1.9 +"use strict";
    1.10 +
    1.11 +let Cu = Components.utils;
    1.12 +let Ci = Components.interfaces;
    1.13 +let Cc = Components.classes;
    1.14 +let Cr = Components.results;
    1.15 +let Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
    1.16 +
    1.17 +this.EXPORTED_SYMBOLS = ["BrowserElementPromptService"];
    1.18 +
    1.19 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.20 +Cu.import("resource://gre/modules/Services.jsm");
    1.21 +
    1.22 +const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
    1.23 +const BROWSER_FRAMES_ENABLED_PREF = "dom.mozBrowserFramesEnabled";
    1.24 +
    1.25 +function debug(msg) {
    1.26 +  //dump("BrowserElementPromptService - " + msg + "\n");
    1.27 +}
    1.28 +
    1.29 +function BrowserElementPrompt(win, browserElementChild) {
    1.30 +  this._win = win;
    1.31 +  this._browserElementChild = browserElementChild;
    1.32 +}
    1.33 +
    1.34 +BrowserElementPrompt.prototype = {
    1.35 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]),
    1.36 +
    1.37 +  alert: function(title, text) {
    1.38 +    this._browserElementChild.showModalPrompt(
    1.39 +      this._win, {promptType: "alert", title: title, message: text, returnValue: undefined});
    1.40 +  },
    1.41 +
    1.42 +  alertCheck: function(title, text, checkMsg, checkState) {
    1.43 +    // Treat this like a normal alert() call, ignoring the checkState.  The
    1.44 +    // front-end can do its own suppression of the alert() if it wants.
    1.45 +    this.alert(title, text);
    1.46 +  },
    1.47 +
    1.48 +  confirm: function(title, text) {
    1.49 +    return this._browserElementChild.showModalPrompt(
    1.50 +      this._win, {promptType: "confirm", title: title, message: text, returnValue: undefined});
    1.51 +  },
    1.52 +
    1.53 +  confirmCheck: function(title, text, checkMsg, checkState) {
    1.54 +    return this.confirm(title, text);
    1.55 +  },
    1.56 +
    1.57 +  // Each button is described by an object with the following schema
    1.58 +  // {
    1.59 +  //   string messageType,  // 'builtin' or 'custom'
    1.60 +  //   string message, // 'ok', 'cancel', 'yes', 'no', 'save', 'dontsave', 
    1.61 +  //                   // 'revert' or a string from caller if messageType was 'custom'.
    1.62 +  // }
    1.63 +  //
    1.64 +  // Expected result from embedder:
    1.65 +  // {
    1.66 +  //   int button, // Index of the button that user pressed.
    1.67 +  //   boolean checked, // True if the check box is checked.
    1.68 +  // }
    1.69 +  confirmEx: function(title, text, buttonFlags, button0Title, button1Title,
    1.70 +                      button2Title, checkMsg, checkState) {
    1.71 +    let buttonProperties = this._buildConfirmExButtonProperties(buttonFlags,
    1.72 +                                                                button0Title,
    1.73 +                                                                button1Title,
    1.74 +                                                                button2Title);
    1.75 +    let defaultReturnValue = { selectedButton: buttonProperties.defaultButton };
    1.76 +    if (checkMsg) {
    1.77 +      defaultReturnValue.checked = checkState.value;
    1.78 +    }
    1.79 +    let ret = this._browserElementChild.showModalPrompt(
    1.80 +      this._win,
    1.81 +      {
    1.82 +        promptType: "custom-prompt",
    1.83 +        title: title,
    1.84 +        message: text,
    1.85 +        defaultButton: buttonProperties.defaultButton,
    1.86 +        buttons: buttonProperties.buttons,
    1.87 +        showCheckbox: !!checkMsg,
    1.88 +        checkboxMessage: checkMsg,
    1.89 +        checkboxCheckedByDefault: !!checkState.value,
    1.90 +        returnValue: defaultReturnValue
    1.91 +      }
    1.92 +    );
    1.93 +    if (checkMsg) {
    1.94 +      checkState.value = ret.checked;
    1.95 +    }
    1.96 +    return buttonProperties.indexToButtonNumberMap[ret.selectedButton];
    1.97 +  },
    1.98 +
    1.99 +  prompt: function(title, text, value, checkMsg, checkState) {
   1.100 +    let rv = this._browserElementChild.showModalPrompt(
   1.101 +      this._win,
   1.102 +      { promptType: "prompt",
   1.103 +        title: title,
   1.104 +        message: text,
   1.105 +        initialValue: value.value,
   1.106 +        returnValue: null });
   1.107 +
   1.108 +    value.value = rv;
   1.109 +
   1.110 +    // nsIPrompt::Prompt returns true if the user pressed "OK" at the prompt,
   1.111 +    // and false if the user pressed "Cancel".
   1.112 +    //
   1.113 +    // BrowserElementChild returns null for "Cancel" and returns the string the
   1.114 +    // user entered otherwise.
   1.115 +    return rv !== null;
   1.116 +  },
   1.117 +
   1.118 +  promptUsernameAndPassword: function(title, text, username, password, checkMsg, checkState) {
   1.119 +    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   1.120 +  },
   1.121 +
   1.122 +  promptPassword: function(title, text, password, checkMsg, checkState) {
   1.123 +    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   1.124 +  },
   1.125 +
   1.126 +  select: function(title, text, aCount, aSelectList, aOutSelection) {
   1.127 +    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   1.128 +  },
   1.129 +
   1.130 +  _buildConfirmExButtonProperties: function(buttonFlags, button0Title,
   1.131 +                                            button1Title, button2Title) {
   1.132 +    let r = {
   1.133 +      defaultButton: -1,
   1.134 +      buttons: [],
   1.135 +      // This map is for translating array index to the button number that
   1.136 +      // is recognized by Gecko. This shouldn't be exposed to embedder.
   1.137 +      indexToButtonNumberMap: []
   1.138 +    };
   1.139 +
   1.140 +    let defaultButton = 0;  // Default to Button 0.
   1.141 +    if (buttonFlags & Ci.nsIPrompt.BUTTON_POS_1_DEFAULT) {
   1.142 +      defaultButton = 1;
   1.143 +    } else if (buttonFlags & Ci.nsIPrompt.BUTTON_POS_2_DEFAULT) {
   1.144 +      defaultButton = 2;
   1.145 +    }
   1.146 +
   1.147 +    // Properties of each button.
   1.148 +    let buttonPositions = [
   1.149 +      Ci.nsIPrompt.BUTTON_POS_0,
   1.150 +      Ci.nsIPrompt.BUTTON_POS_1,
   1.151 +      Ci.nsIPrompt.BUTTON_POS_2
   1.152 +    ];
   1.153 +
   1.154 +    function buildButton(buttonTitle, buttonNumber) {
   1.155 +      let ret = {};
   1.156 +      let buttonPosition = buttonPositions[buttonNumber];
   1.157 +      let mask = 0xff * buttonPosition;  // 8 bit mask
   1.158 +      let titleType = (buttonFlags & mask) / buttonPosition;
   1.159 +
   1.160 +      ret.messageType = 'builtin';
   1.161 +      switch(titleType) {
   1.162 +      case Ci.nsIPrompt.BUTTON_TITLE_OK:
   1.163 +        ret.message = 'ok';
   1.164 +        break;
   1.165 +      case Ci.nsIPrompt.BUTTON_TITLE_CANCEL:
   1.166 +        ret.message = 'cancel';
   1.167 +        break;
   1.168 +      case Ci.nsIPrompt.BUTTON_TITLE_YES:
   1.169 +        ret.message = 'yes';
   1.170 +        break;
   1.171 +      case Ci.nsIPrompt.BUTTON_TITLE_NO:
   1.172 +        ret.message = 'no';
   1.173 +        break;
   1.174 +      case Ci.nsIPrompt.BUTTON_TITLE_SAVE:
   1.175 +        ret.message = 'save';
   1.176 +        break;
   1.177 +      case Ci.nsIPrompt.BUTTON_TITLE_DONT_SAVE:
   1.178 +        ret.message = 'dontsave';
   1.179 +        break;
   1.180 +      case Ci.nsIPrompt.BUTTON_TITLE_REVERT:
   1.181 +        ret.message = 'revert';
   1.182 +        break;
   1.183 +      case Ci.nsIPrompt.BUTTON_TITLE_IS_STRING:
   1.184 +        ret.message = buttonTitle;
   1.185 +        ret.messageType = 'custom';
   1.186 +        break;
   1.187 +      default:
   1.188 +        // This button is not shown.
   1.189 +        return;
   1.190 +      }
   1.191 +
   1.192 +      // If this is the default button, set r.defaultButton to
   1.193 +      // the index of this button in the array. This value is going to be
   1.194 +      // exposed to the embedder.
   1.195 +      if (defaultButton === buttonNumber) {
   1.196 +        r.defaultButton = r.buttons.length;
   1.197 +      }
   1.198 +      r.buttons.push(ret);
   1.199 +      r.indexToButtonNumberMap.push(buttonNumber);
   1.200 +    }
   1.201 +
   1.202 +    buildButton(button0Title, 0);
   1.203 +    buildButton(button1Title, 1);
   1.204 +    buildButton(button2Title, 2);
   1.205 +
   1.206 +    // If defaultButton is still -1 here, it means the default button won't
   1.207 +    // be shown.
   1.208 +    if (r.defaultButton === -1) {
   1.209 +      throw new Components.Exception("Default button won't be shown",
   1.210 +                                     Cr.NS_ERROR_FAILURE);
   1.211 +    }
   1.212 +
   1.213 +    return r;
   1.214 +  },
   1.215 +};
   1.216 +
   1.217 +
   1.218 +function BrowserElementAuthPrompt() {
   1.219 +}
   1.220 +
   1.221 +BrowserElementAuthPrompt.prototype = {
   1.222 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAuthPrompt2]),
   1.223 +
   1.224 +  promptAuth: function promptAuth(channel, level, authInfo) {
   1.225 +    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   1.226 +  },
   1.227 +
   1.228 +  asyncPromptAuth: function asyncPromptAuth(channel, callback, context, level, authInfo) {
   1.229 +    debug("asyncPromptAuth");
   1.230 +
   1.231 +    // The cases that we don't support now.
   1.232 +    if ((authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) &&
   1.233 +        (authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD)) {
   1.234 +      throw Cr.NS_ERROR_FAILURE;
   1.235 +    }
   1.236 +
   1.237 +    let frame = this._getFrameFromChannel(channel);
   1.238 +    if (!frame) {
   1.239 +      debug("Cannot get frame, asyncPromptAuth fail");
   1.240 +      throw Cr.NS_ERROR_FAILURE;
   1.241 +    }
   1.242 +
   1.243 +    let browserElementParent =
   1.244 +      BrowserElementPromptService.getBrowserElementParentForFrame(frame);
   1.245 +
   1.246 +    if (!browserElementParent) {
   1.247 +      debug("Failed to load browser element parent.");
   1.248 +      throw Cr.NS_ERROR_FAILURE;
   1.249 +    }
   1.250 +
   1.251 +    let consumer = {
   1.252 +      QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
   1.253 +      callback: callback,
   1.254 +      context: context,
   1.255 +      cancel: function() {
   1.256 +        this.callback.onAuthCancelled(this.context, false);
   1.257 +        this.callback = null;
   1.258 +        this.context = null;
   1.259 +      }
   1.260 +    };
   1.261 +
   1.262 +    let [hostname, httpRealm] = this._getAuthTarget(channel, authInfo);
   1.263 +    let hashKey = level + "|" + hostname + "|" + httpRealm;
   1.264 +    let asyncPrompt = this._asyncPrompts[hashKey];
   1.265 +    if (asyncPrompt) {
   1.266 +      asyncPrompt.consumers.push(consumer);
   1.267 +      return consumer;
   1.268 +    }
   1.269 +
   1.270 +    asyncPrompt = {
   1.271 +      consumers: [consumer],
   1.272 +      channel: channel,
   1.273 +      authInfo: authInfo,
   1.274 +      level: level,
   1.275 +      inProgress: false,
   1.276 +      browserElementParent: browserElementParent
   1.277 +    };
   1.278 +
   1.279 +    this._asyncPrompts[hashKey] = asyncPrompt;
   1.280 +    this._doAsyncPrompt();
   1.281 +    return consumer;
   1.282 +  },
   1.283 +
   1.284 +  // Utilities for nsIAuthPrompt2 ----------------
   1.285 +
   1.286 +  _asyncPrompts: {},
   1.287 +  _asyncPromptInProgress: new WeakMap(),
   1.288 +  _doAsyncPrompt: function() {
   1.289 +    // Find the key of a prompt whose browser element parent does not have
   1.290 +    // async prompt in progress.
   1.291 +    let hashKey = null;
   1.292 +    for (let key in this._asyncPrompts) {
   1.293 +      let prompt = this._asyncPrompts[key];
   1.294 +      if (!this._asyncPromptInProgress.get(prompt.browserElementParent)) {
   1.295 +        hashKey = key;
   1.296 +        break;
   1.297 +      }
   1.298 +    }
   1.299 +
   1.300 +    // Didn't find an available prompt, so just return.
   1.301 +    if (!hashKey)
   1.302 +      return;
   1.303 +
   1.304 +    let prompt = this._asyncPrompts[hashKey];
   1.305 +    let [hostname, httpRealm] = this._getAuthTarget(prompt.channel,
   1.306 +                                                    prompt.authInfo);
   1.307 +
   1.308 +    this._asyncPromptInProgress.set(prompt.browserElementParent, true);
   1.309 +    prompt.inProgress = true;
   1.310 +
   1.311 +    let self = this;
   1.312 +    let callback = function(ok, username, password) {
   1.313 +      debug("Async auth callback is called, ok = " +
   1.314 +            ok + ", username = " + username);
   1.315 +
   1.316 +      // Here we got the username and password provided by embedder, or
   1.317 +      // ok = false if the prompt was cancelled by embedder.
   1.318 +      delete self._asyncPrompts[hashKey];
   1.319 +      prompt.inProgress = false;
   1.320 +      self._asyncPromptInProgress.delete(prompt.browserElementParent);
   1.321 +
   1.322 +      // Fill authentication information with username and password provided
   1.323 +      // by user.
   1.324 +      let flags = prompt.authInfo.flags;
   1.325 +      if (username) {
   1.326 +        if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
   1.327 +          // Domain is separated from username by a backslash
   1.328 +          let idx = username.indexOf("\\");
   1.329 +          if (idx == -1) {
   1.330 +            prompt.authInfo.username = username;
   1.331 +          } else {
   1.332 +            prompt.authInfo.domain   = username.substring(0, idx);
   1.333 +            prompt.authInfo.username = username.substring(idx + 1);
   1.334 +          }
   1.335 +        } else {
   1.336 +          prompt.authInfo.username = username;
   1.337 +        }
   1.338 +      }
   1.339 +
   1.340 +      if (password) {
   1.341 +        prompt.authInfo.password = password;
   1.342 +      }
   1.343 +
   1.344 +      for each (let consumer in prompt.consumers) {
   1.345 +        if (!consumer.callback) {
   1.346 +          // Not having a callback means that consumer didn't provide it
   1.347 +          // or canceled the notification.
   1.348 +          continue;
   1.349 +        }
   1.350 +
   1.351 +        try {
   1.352 +          if (ok) {
   1.353 +            debug("Ok, calling onAuthAvailable to finish auth");
   1.354 +            consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo);
   1.355 +          } else {
   1.356 +            debug("Cancelled, calling onAuthCancelled to finish auth.");
   1.357 +            consumer.callback.onAuthCancelled(consumer.context, true);
   1.358 +          }
   1.359 +        } catch (e) { /* Throw away exceptions caused by callback */ }
   1.360 +      }
   1.361 +
   1.362 +      // Process the next prompt, if one is pending.
   1.363 +      self._doAsyncPrompt();
   1.364 +    };
   1.365 +
   1.366 +    let runnable = {
   1.367 +      run: function() {
   1.368 +        // Call promptAuth of browserElementParent, to show the prompt.
   1.369 +        prompt.browserElementParent.promptAuth(
   1.370 +          self._createAuthDetail(prompt.channel, prompt.authInfo),
   1.371 +          callback);
   1.372 +      }
   1.373 +    }
   1.374 +
   1.375 +    Services.tm.currentThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL);
   1.376 +  },
   1.377 +
   1.378 +  _getFrameFromChannel: function(channel) {
   1.379 +    let loadContext = channel.notificationCallbacks.getInterface(Ci.nsILoadContext);
   1.380 +    return loadContext.topFrameElement;
   1.381 +  },
   1.382 +
   1.383 +  _createAuthDetail: function(channel, authInfo) {
   1.384 +    let [hostname, httpRealm] = this._getAuthTarget(channel, authInfo);
   1.385 +    return {
   1.386 +      host:             hostname,
   1.387 +      realm:            httpRealm,
   1.388 +      username:         authInfo.username,
   1.389 +      isOnlyPassword:   !!(authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD)
   1.390 +    };
   1.391 +  },
   1.392 +
   1.393 +  _getAuthTarget : function (channel, authInfo) {
   1.394 +    let hostname = this._getFormattedHostname(channel.URI);
   1.395 +
   1.396 +    // If a HTTP WWW-Authenticate header specified a realm, that value
   1.397 +    // will be available here. If it wasn't set or wasn't HTTP, we'll use
   1.398 +    // the formatted hostname instead.
   1.399 +    let realm = authInfo.realm;
   1.400 +    if (!realm)
   1.401 +      realm = hostname;
   1.402 +
   1.403 +    return [hostname, realm];
   1.404 +  },
   1.405 +
   1.406 +  _getFormattedHostname : function(uri) {
   1.407 +    let scheme = uri.scheme;
   1.408 +    let hostname = scheme + "://" + uri.host;
   1.409 +
   1.410 +    // If the URI explicitly specified a port, only include it when
   1.411 +    // it's not the default. (We never want "http://foo.com:80")
   1.412 +    let port = uri.port;
   1.413 +    if (port != -1) {
   1.414 +      let handler = Services.io.getProtocolHandler(scheme);
   1.415 +      if (port != handler.defaultPort)
   1.416 +        hostname += ":" + port;
   1.417 +    }
   1.418 +    return hostname;
   1.419 +  }
   1.420 +};
   1.421 +
   1.422 +
   1.423 +function AuthPromptWrapper(oldImpl, browserElementImpl) {
   1.424 +  this._oldImpl = oldImpl;
   1.425 +  this._browserElementImpl = browserElementImpl;
   1.426 +}
   1.427 +
   1.428 +AuthPromptWrapper.prototype = {
   1.429 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAuthPrompt2]),
   1.430 +  promptAuth: function(channel, level, authInfo) {
   1.431 +    if (this._canGetParentElement(channel)) {
   1.432 +      return this._browserElementImpl.promptAuth(channel, level, authInfo);
   1.433 +    } else {
   1.434 +      return this._oldImpl.promptAuth(channel, level, authInfo);
   1.435 +    }
   1.436 +  },
   1.437 +
   1.438 +  asyncPromptAuth: function(channel, callback, context, level, authInfo) {
   1.439 +    if (this._canGetParentElement(channel)) {
   1.440 +      return this._browserElementImpl.asyncPromptAuth(channel, callback, context, level, authInfo);
   1.441 +    } else {
   1.442 +      return this._oldImpl.asyncPromptAuth(channel, callback, context, level, authInfo);
   1.443 +    }
   1.444 +  },
   1.445 +
   1.446 +  _canGetParentElement: function(channel) {
   1.447 +    try {
   1.448 +      let frame = channel.notificationCallbacks.getInterface(Ci.nsILoadContext).topFrameElement;
   1.449 +      if (!frame)
   1.450 +        return false;
   1.451 +
   1.452 +      if (!BrowserElementPromptService.getBrowserElementParentForFrame(frame))
   1.453 +        return false;
   1.454 +
   1.455 +      return true;
   1.456 +    } catch (e) {
   1.457 +      return false;
   1.458 +    }
   1.459 +  }
   1.460 +};
   1.461 +
   1.462 +function BrowserElementPromptFactory(toWrap) {
   1.463 +  this._wrapped = toWrap;
   1.464 +}
   1.465 +
   1.466 +BrowserElementPromptFactory.prototype = {
   1.467 +  classID: Components.ID("{24f3d0cf-e417-4b85-9017-c9ecf8bb1299}"),
   1.468 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptFactory]),
   1.469 +
   1.470 +  _mayUseNativePrompt: function() {
   1.471 +    try {
   1.472 +      return Services.prefs.getBoolPref("browser.prompt.allowNative");
   1.473 +    } catch (e) {
   1.474 +      // This properity is default to true.
   1.475 +      return true;
   1.476 +    }
   1.477 +  },
   1.478 +
   1.479 +  _getNativePromptIfAllowed: function(win, iid, err) {
   1.480 +    if (this._mayUseNativePrompt())
   1.481 +      return this._wrapped.getPrompt(win, iid);
   1.482 +    else {
   1.483 +      // Not allowed, throw an exception.
   1.484 +      throw err;
   1.485 +    }
   1.486 +  },
   1.487 +
   1.488 +  getPrompt: function(win, iid) {
   1.489 +    // It is possible for some object to get a prompt without passing
   1.490 +    // valid reference of window, like nsNSSComponent. In such case, we
   1.491 +    // should just fall back to the native prompt service
   1.492 +    if (!win)
   1.493 +      return this._getNativePromptIfAllowed(win, iid, Cr.NS_ERROR_INVALID_ARG);
   1.494 +
   1.495 +    if (iid.number != Ci.nsIPrompt.number &&
   1.496 +        iid.number != Ci.nsIAuthPrompt2.number) {
   1.497 +      debug("We don't recognize the requested IID (" + iid + ", " +
   1.498 +            "allowed IID: " +
   1.499 +            "nsIPrompt=" + Ci.nsIPrompt + ", " +
   1.500 +            "nsIAuthPrompt2=" + Ci.nsIAuthPrompt2 + ")");
   1.501 +      return this._getNativePromptIfAllowed(win, iid, Cr.NS_ERROR_INVALID_ARG);
   1.502 +    }
   1.503 +
   1.504 +    // Try to find a BrowserElementChild for the window.
   1.505 +    let browserElementChild =
   1.506 +      BrowserElementPromptService.getBrowserElementChildForWindow(win);
   1.507 +
   1.508 +    if (iid.number === Ci.nsIAuthPrompt2.number) {
   1.509 +      debug("Caller requests an instance of nsIAuthPrompt2.");
   1.510 +
   1.511 +      if (browserElementChild) {
   1.512 +        // If we are able to get a BrowserElementChild, it means that
   1.513 +        // the auth prompt is for a mozbrowser. Therefore we don't need to
   1.514 +        // fall back.
   1.515 +        return new BrowserElementAuthPrompt().QueryInterface(iid);
   1.516 +      }
   1.517 +
   1.518 +      // Because nsIAuthPrompt2 is called in parent process. If caller
   1.519 +      // wants nsIAuthPrompt2 and we cannot get BrowserElementchild,
   1.520 +      // it doesn't mean that we should fallback. It is possible that we can
   1.521 +      // get the BrowserElementParent from nsIChannel that passed to
   1.522 +      // functions of nsIAuthPrompt2.
   1.523 +      if (this._mayUseNativePrompt()) {
   1.524 +        return new AuthPromptWrapper(
   1.525 +            this._wrapped.getPrompt(win, iid),
   1.526 +            new BrowserElementAuthPrompt().QueryInterface(iid))
   1.527 +          .QueryInterface(iid);
   1.528 +      } else {
   1.529 +        // Falling back is not allowed, so we don't need wrap the
   1.530 +        // BrowserElementPrompt.
   1.531 +        return new BrowserElementAuthPrompt().QueryInterface(iid);
   1.532 +      }
   1.533 +    }
   1.534 +
   1.535 +    if (!browserElementChild) {
   1.536 +      debug("We can't find a browserElementChild for " +
   1.537 +            win + ", " + win.location);
   1.538 +      return this._getNativePromptIfAllowed(win, iid, Cr.NS_ERROR_FAILURE);
   1.539 +    }
   1.540 +
   1.541 +    debug("Returning wrapped getPrompt for " + win);
   1.542 +    return new BrowserElementPrompt(win, browserElementChild)
   1.543 +                                   .QueryInterface(iid);
   1.544 +  }
   1.545 +};
   1.546 +
   1.547 +this.BrowserElementPromptService = {
   1.548 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
   1.549 +                                         Ci.nsISupportsWeakReference]),
   1.550 +
   1.551 +  _initialized: false,
   1.552 +
   1.553 +  _init: function() {
   1.554 +    if (this._initialized) {
   1.555 +      return;
   1.556 +    }
   1.557 +
   1.558 +    // If the pref is disabled, do nothing except wait for the pref to change.
   1.559 +    if (!this._browserFramesPrefEnabled()) {
   1.560 +      var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
   1.561 +      prefs.addObserver(BROWSER_FRAMES_ENABLED_PREF, this, /* ownsWeak = */ true);
   1.562 +      return;
   1.563 +    }
   1.564 +
   1.565 +    this._initialized = true;
   1.566 +    this._browserElementParentMap = new WeakMap();
   1.567 +
   1.568 +    var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
   1.569 +    os.addObserver(this, "outer-window-destroyed", /* ownsWeak = */ true);
   1.570 +
   1.571 +    // Wrap the existing @mozilla.org/prompter;1 implementation.
   1.572 +    var contractID = "@mozilla.org/prompter;1";
   1.573 +    var oldCID = Cm.contractIDToCID(contractID);
   1.574 +    var newCID = BrowserElementPromptFactory.prototype.classID;
   1.575 +    var oldFactory = Cm.getClassObject(Cc[contractID], Ci.nsIFactory);
   1.576 +
   1.577 +    if (oldCID == newCID) {
   1.578 +      debug("WARNING: Wrapped prompt factory is already installed!");
   1.579 +      return;
   1.580 +    }
   1.581 +
   1.582 +    Cm.unregisterFactory(oldCID, oldFactory);
   1.583 +
   1.584 +    var oldInstance = oldFactory.createInstance(null, Ci.nsIPromptFactory);
   1.585 +    var newInstance = new BrowserElementPromptFactory(oldInstance);
   1.586 +
   1.587 +    var newFactory = {
   1.588 +      createInstance: function(outer, iid) {
   1.589 +        if (outer != null) {
   1.590 +          throw Cr.NS_ERROR_NO_AGGREGATION;
   1.591 +        }
   1.592 +        return newInstance.QueryInterface(iid);
   1.593 +      }
   1.594 +    };
   1.595 +    Cm.registerFactory(newCID,
   1.596 +                       "BrowserElementPromptService's prompter;1 wrapper",
   1.597 +                       contractID, newFactory);
   1.598 +
   1.599 +    debug("Done installing new prompt factory.");
   1.600 +  },
   1.601 +
   1.602 +  _getOuterWindowID: function(win) {
   1.603 +    return win.QueryInterface(Ci.nsIInterfaceRequestor)
   1.604 +              .getInterface(Ci.nsIDOMWindowUtils)
   1.605 +              .outerWindowID;
   1.606 +  },
   1.607 +
   1.608 +  _browserElementChildMap: {},
   1.609 +  mapWindowToBrowserElementChild: function(win, browserElementChild) {
   1.610 +    this._browserElementChildMap[this._getOuterWindowID(win)] = browserElementChild;
   1.611 +  },
   1.612 +
   1.613 +  getBrowserElementChildForWindow: function(win) {
   1.614 +    // We only have a mapping for <iframe mozbrowser>s, not their inner
   1.615 +    // <iframes>, so we look up win.top below.  window.top (when called from
   1.616 +    // script) respects <iframe mozbrowser> boundaries.
   1.617 +    return this._browserElementChildMap[this._getOuterWindowID(win.top)];
   1.618 +  },
   1.619 +
   1.620 +  mapFrameToBrowserElementParent: function(frame, browserElementParent) {
   1.621 +    this._browserElementParentMap.set(frame, browserElementParent);
   1.622 +  },
   1.623 +
   1.624 +  getBrowserElementParentForFrame: function(frame) {
   1.625 +    return this._browserElementParentMap.get(frame);
   1.626 +  },
   1.627 +
   1.628 +  _observeOuterWindowDestroyed: function(outerWindowID) {
   1.629 +    let id = outerWindowID.QueryInterface(Ci.nsISupportsPRUint64).data;
   1.630 +    debug("observeOuterWindowDestroyed " + id);
   1.631 +    delete this._browserElementChildMap[outerWindowID.data];
   1.632 +  },
   1.633 +
   1.634 +  _browserFramesPrefEnabled: function() {
   1.635 +    var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
   1.636 +    try {
   1.637 +      return prefs.getBoolPref(BROWSER_FRAMES_ENABLED_PREF);
   1.638 +    }
   1.639 +    catch(e) {
   1.640 +      return false;
   1.641 +    }
   1.642 +  },
   1.643 +
   1.644 +  observe: function(subject, topic, data) {
   1.645 +    switch(topic) {
   1.646 +    case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
   1.647 +      if (data == BROWSER_FRAMES_ENABLED_PREF) {
   1.648 +        this._init();
   1.649 +      }
   1.650 +      break;
   1.651 +    case "outer-window-destroyed":
   1.652 +      this._observeOuterWindowDestroyed(subject);
   1.653 +      break;
   1.654 +    default:
   1.655 +      debug("Observed unexpected topic " + topic);
   1.656 +    }
   1.657 +  }
   1.658 +};
   1.659 +
   1.660 +BrowserElementPromptService._init();

mercurial