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();