Wed, 31 Dec 2014 06:55:50 +0100
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(); |