summaryrefslogtreecommitdiffstats
path: root/dom/browser-element/BrowserElementPromptService.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'dom/browser-element/BrowserElementPromptService.jsm')
-rw-r--r--dom/browser-element/BrowserElementPromptService.jsm685
1 files changed, 685 insertions, 0 deletions
diff --git a/dom/browser-element/BrowserElementPromptService.jsm b/dom/browser-element/BrowserElementPromptService.jsm
new file mode 100644
index 000000000..1442304db
--- /dev/null
+++ b/dom/browser-element/BrowserElementPromptService.jsm
@@ -0,0 +1,685 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* vim: set ft=javascript : */
+
+"use strict";
+
+var Cu = Components.utils;
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cr = Components.results;
+var Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+this.EXPORTED_SYMBOLS = ["BrowserElementPromptService"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
+const BROWSER_FRAMES_ENABLED_PREF = "dom.mozBrowserFramesEnabled";
+
+function debug(msg) {
+ //dump("BrowserElementPromptService - " + msg + "\n");
+}
+
+function BrowserElementPrompt(win, browserElementChild) {
+ this._win = win;
+ this._browserElementChild = browserElementChild;
+}
+
+BrowserElementPrompt.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]),
+
+ alert: function(title, text) {
+ this._browserElementChild.showModalPrompt(
+ this._win, {promptType: "alert", title: title, message: text, returnValue: undefined});
+ },
+
+ alertCheck: function(title, text, checkMsg, checkState) {
+ // Treat this like a normal alert() call, ignoring the checkState. The
+ // front-end can do its own suppression of the alert() if it wants.
+ this.alert(title, text);
+ },
+
+ confirm: function(title, text) {
+ return this._browserElementChild.showModalPrompt(
+ this._win, {promptType: "confirm", title: title, message: text, returnValue: undefined});
+ },
+
+ confirmCheck: function(title, text, checkMsg, checkState) {
+ return this.confirm(title, text);
+ },
+
+ // Each button is described by an object with the following schema
+ // {
+ // string messageType, // 'builtin' or 'custom'
+ // string message, // 'ok', 'cancel', 'yes', 'no', 'save', 'dontsave',
+ // // 'revert' or a string from caller if messageType was 'custom'.
+ // }
+ //
+ // Expected result from embedder:
+ // {
+ // int button, // Index of the button that user pressed.
+ // boolean checked, // True if the check box is checked.
+ // }
+ confirmEx: function(title, text, buttonFlags, button0Title, button1Title,
+ button2Title, checkMsg, checkState) {
+ let buttonProperties = this._buildConfirmExButtonProperties(buttonFlags,
+ button0Title,
+ button1Title,
+ button2Title);
+ let defaultReturnValue = { selectedButton: buttonProperties.defaultButton };
+ if (checkMsg) {
+ defaultReturnValue.checked = checkState.value;
+ }
+ let ret = this._browserElementChild.showModalPrompt(
+ this._win,
+ {
+ promptType: "custom-prompt",
+ title: title,
+ message: text,
+ defaultButton: buttonProperties.defaultButton,
+ buttons: buttonProperties.buttons,
+ showCheckbox: !!checkMsg,
+ checkboxMessage: checkMsg,
+ checkboxCheckedByDefault: !!checkState.value,
+ returnValue: defaultReturnValue
+ }
+ );
+ if (checkMsg) {
+ checkState.value = ret.checked;
+ }
+ return buttonProperties.indexToButtonNumberMap[ret.selectedButton];
+ },
+
+ prompt: function(title, text, value, checkMsg, checkState) {
+ let rv = this._browserElementChild.showModalPrompt(
+ this._win,
+ { promptType: "prompt",
+ title: title,
+ message: text,
+ initialValue: value.value,
+ returnValue: null });
+
+ value.value = rv;
+
+ // nsIPrompt::Prompt returns true if the user pressed "OK" at the prompt,
+ // and false if the user pressed "Cancel".
+ //
+ // BrowserElementChild returns null for "Cancel" and returns the string the
+ // user entered otherwise.
+ return rv !== null;
+ },
+
+ promptUsernameAndPassword: function(title, text, username, password, checkMsg, checkState) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ promptPassword: function(title, text, password, checkMsg, checkState) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ select: function(title, text, aCount, aSelectList, aOutSelection) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ _buildConfirmExButtonProperties: function(buttonFlags, button0Title,
+ button1Title, button2Title) {
+ let r = {
+ defaultButton: -1,
+ buttons: [],
+ // This map is for translating array index to the button number that
+ // is recognized by Gecko. This shouldn't be exposed to embedder.
+ indexToButtonNumberMap: []
+ };
+
+ let defaultButton = 0; // Default to Button 0.
+ if (buttonFlags & Ci.nsIPrompt.BUTTON_POS_1_DEFAULT) {
+ defaultButton = 1;
+ } else if (buttonFlags & Ci.nsIPrompt.BUTTON_POS_2_DEFAULT) {
+ defaultButton = 2;
+ }
+
+ // Properties of each button.
+ let buttonPositions = [
+ Ci.nsIPrompt.BUTTON_POS_0,
+ Ci.nsIPrompt.BUTTON_POS_1,
+ Ci.nsIPrompt.BUTTON_POS_2
+ ];
+
+ function buildButton(buttonTitle, buttonNumber) {
+ let ret = {};
+ let buttonPosition = buttonPositions[buttonNumber];
+ let mask = 0xff * buttonPosition; // 8 bit mask
+ let titleType = (buttonFlags & mask) / buttonPosition;
+
+ ret.messageType = 'builtin';
+ switch(titleType) {
+ case Ci.nsIPrompt.BUTTON_TITLE_OK:
+ ret.message = 'ok';
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_CANCEL:
+ ret.message = 'cancel';
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_YES:
+ ret.message = 'yes';
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_NO:
+ ret.message = 'no';
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_SAVE:
+ ret.message = 'save';
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_DONT_SAVE:
+ ret.message = 'dontsave';
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_REVERT:
+ ret.message = 'revert';
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_IS_STRING:
+ ret.message = buttonTitle;
+ ret.messageType = 'custom';
+ break;
+ default:
+ // This button is not shown.
+ return;
+ }
+
+ // If this is the default button, set r.defaultButton to
+ // the index of this button in the array. This value is going to be
+ // exposed to the embedder.
+ if (defaultButton === buttonNumber) {
+ r.defaultButton = r.buttons.length;
+ }
+ r.buttons.push(ret);
+ r.indexToButtonNumberMap.push(buttonNumber);
+ }
+
+ buildButton(button0Title, 0);
+ buildButton(button1Title, 1);
+ buildButton(button2Title, 2);
+
+ // If defaultButton is still -1 here, it means the default button won't
+ // be shown.
+ if (r.defaultButton === -1) {
+ throw new Components.Exception("Default button won't be shown",
+ Cr.NS_ERROR_FAILURE);
+ }
+
+ return r;
+ },
+};
+
+
+function BrowserElementAuthPrompt() {
+}
+
+BrowserElementAuthPrompt.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAuthPrompt2]),
+
+ promptAuth: function promptAuth(channel, level, authInfo) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ asyncPromptAuth: function asyncPromptAuth(channel, callback, context, level, authInfo) {
+ debug("asyncPromptAuth");
+
+ // The cases that we don't support now.
+ if ((authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) &&
+ (authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD)) {
+ throw Cr.NS_ERROR_FAILURE;
+ }
+
+ let frame = this._getFrameFromChannel(channel);
+ if (!frame) {
+ debug("Cannot get frame, asyncPromptAuth fail");
+ throw Cr.NS_ERROR_FAILURE;
+ }
+
+ let browserElementParent =
+ BrowserElementPromptService.getBrowserElementParentForFrame(frame);
+
+ if (!browserElementParent) {
+ debug("Failed to load browser element parent.");
+ throw Cr.NS_ERROR_FAILURE;
+ }
+
+ let consumer = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
+ callback: callback,
+ context: context,
+ cancel: function() {
+ this.callback.onAuthCancelled(this.context, false);
+ this.callback = null;
+ this.context = null;
+ }
+ };
+
+ let [hostname, httpRealm] = this._getAuthTarget(channel, authInfo);
+ let hashKey = level + "|" + hostname + "|" + httpRealm;
+ let asyncPrompt = this._asyncPrompts[hashKey];
+ if (asyncPrompt) {
+ asyncPrompt.consumers.push(consumer);
+ return consumer;
+ }
+
+ asyncPrompt = {
+ consumers: [consumer],
+ channel: channel,
+ authInfo: authInfo,
+ level: level,
+ inProgress: false,
+ browserElementParent: browserElementParent
+ };
+
+ this._asyncPrompts[hashKey] = asyncPrompt;
+ this._doAsyncPrompt();
+ return consumer;
+ },
+
+ // Utilities for nsIAuthPrompt2 ----------------
+
+ _asyncPrompts: {},
+ _asyncPromptInProgress: new WeakMap(),
+ _doAsyncPrompt: function() {
+ // Find the key of a prompt whose browser element parent does not have
+ // async prompt in progress.
+ let hashKey = null;
+ for (let key in this._asyncPrompts) {
+ let prompt = this._asyncPrompts[key];
+ if (!this._asyncPromptInProgress.get(prompt.browserElementParent)) {
+ hashKey = key;
+ break;
+ }
+ }
+
+ // Didn't find an available prompt, so just return.
+ if (!hashKey)
+ return;
+
+ let prompt = this._asyncPrompts[hashKey];
+ let [hostname, httpRealm] = this._getAuthTarget(prompt.channel,
+ prompt.authInfo);
+
+ this._asyncPromptInProgress.set(prompt.browserElementParent, true);
+ prompt.inProgress = true;
+
+ let self = this;
+ let callback = function(ok, username, password) {
+ debug("Async auth callback is called, ok = " +
+ ok + ", username = " + username);
+
+ // Here we got the username and password provided by embedder, or
+ // ok = false if the prompt was cancelled by embedder.
+ delete self._asyncPrompts[hashKey];
+ prompt.inProgress = false;
+ self._asyncPromptInProgress.delete(prompt.browserElementParent);
+
+ // Fill authentication information with username and password provided
+ // by user.
+ let flags = prompt.authInfo.flags;
+ if (username) {
+ if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
+ // Domain is separated from username by a backslash
+ let idx = username.indexOf("\\");
+ if (idx == -1) {
+ prompt.authInfo.username = username;
+ } else {
+ prompt.authInfo.domain = username.substring(0, idx);
+ prompt.authInfo.username = username.substring(idx + 1);
+ }
+ } else {
+ prompt.authInfo.username = username;
+ }
+ }
+
+ if (password) {
+ prompt.authInfo.password = password;
+ }
+
+ for (let consumer of prompt.consumers) {
+ if (!consumer.callback) {
+ // Not having a callback means that consumer didn't provide it
+ // or canceled the notification.
+ continue;
+ }
+
+ try {
+ if (ok) {
+ debug("Ok, calling onAuthAvailable to finish auth");
+ consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo);
+ } else {
+ debug("Cancelled, calling onAuthCancelled to finish auth.");
+ consumer.callback.onAuthCancelled(consumer.context, true);
+ }
+ } catch (e) { /* Throw away exceptions caused by callback */ }
+ }
+
+ // Process the next prompt, if one is pending.
+ self._doAsyncPrompt();
+ };
+
+ let runnable = {
+ run: function() {
+ // Call promptAuth of browserElementParent, to show the prompt.
+ prompt.browserElementParent.promptAuth(
+ self._createAuthDetail(prompt.channel, prompt.authInfo),
+ callback);
+ }
+ }
+
+ Services.tm.currentThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL);
+ },
+
+ _getFrameFromChannel: function(channel) {
+ let loadContext = channel.notificationCallbacks.getInterface(Ci.nsILoadContext);
+ return loadContext.topFrameElement;
+ },
+
+ _createAuthDetail: function(channel, authInfo) {
+ let [hostname, httpRealm] = this._getAuthTarget(channel, authInfo);
+ return {
+ host: hostname,
+ path: channel.URI.path,
+ realm: httpRealm,
+ username: authInfo.username,
+ isProxy: !!(authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY),
+ isOnlyPassword: !!(authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD)
+ };
+ },
+
+ // The code is taken from nsLoginManagerPrompter.js, with slight
+ // modification for parameter name consistency here.
+ _getAuthTarget : function (channel, authInfo) {
+ let hostname, realm;
+
+ // If our proxy is demanding authentication, don't use the
+ // channel's actual destination.
+ if (authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
+ if (!(channel instanceof Ci.nsIProxiedChannel))
+ throw new Error("proxy auth needs nsIProxiedChannel");
+
+ let info = channel.proxyInfo;
+ if (!info)
+ throw new Error("proxy auth needs nsIProxyInfo");
+
+ // Proxies don't have a scheme, but we'll use "moz-proxy://"
+ // so that it's more obvious what the login is for.
+ var idnService = Cc["@mozilla.org/network/idn-service;1"].
+ getService(Ci.nsIIDNService);
+ hostname = "moz-proxy://" +
+ idnService.convertUTF8toACE(info.host) +
+ ":" + info.port;
+ realm = authInfo.realm;
+ if (!realm)
+ realm = hostname;
+
+ return [hostname, realm];
+ }
+
+ hostname = this._getFormattedHostname(channel.URI);
+
+ // If a HTTP WWW-Authenticate header specified a realm, that value
+ // will be available here. If it wasn't set or wasn't HTTP, we'll use
+ // the formatted hostname instead.
+ realm = authInfo.realm;
+ if (!realm)
+ realm = hostname;
+
+ return [hostname, realm];
+ },
+
+ /**
+ * Strip out things like userPass and path for display.
+ */
+ _getFormattedHostname : function(uri) {
+ return uri.scheme + "://" + uri.hostPort;
+ },
+};
+
+
+function AuthPromptWrapper(oldImpl, browserElementImpl) {
+ this._oldImpl = oldImpl;
+ this._browserElementImpl = browserElementImpl;
+}
+
+AuthPromptWrapper.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAuthPrompt2]),
+ promptAuth: function(channel, level, authInfo) {
+ if (this._canGetParentElement(channel)) {
+ return this._browserElementImpl.promptAuth(channel, level, authInfo);
+ } else {
+ return this._oldImpl.promptAuth(channel, level, authInfo);
+ }
+ },
+
+ asyncPromptAuth: function(channel, callback, context, level, authInfo) {
+ if (this._canGetParentElement(channel)) {
+ return this._browserElementImpl.asyncPromptAuth(channel, callback, context, level, authInfo);
+ } else {
+ return this._oldImpl.asyncPromptAuth(channel, callback, context, level, authInfo);
+ }
+ },
+
+ _canGetParentElement: function(channel) {
+ try {
+ let context = channel.notificationCallbacks.getInterface(Ci.nsILoadContext);
+ let frame = context.topFrameElement;
+ if (!frame) {
+ // This function returns a boolean value
+ return !!context.nestedFrameId;
+ }
+
+ if (!BrowserElementPromptService.getBrowserElementParentForFrame(frame))
+ return false;
+
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+};
+
+function BrowserElementPromptFactory(toWrap) {
+ this._wrapped = toWrap;
+}
+
+BrowserElementPromptFactory.prototype = {
+ classID: Components.ID("{24f3d0cf-e417-4b85-9017-c9ecf8bb1299}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptFactory]),
+
+ _mayUseNativePrompt: function() {
+ try {
+ return Services.prefs.getBoolPref("browser.prompt.allowNative");
+ } catch (e) {
+ // This properity is default to true.
+ return true;
+ }
+ },
+
+ _getNativePromptIfAllowed: function(win, iid, err) {
+ if (this._mayUseNativePrompt())
+ return this._wrapped.getPrompt(win, iid);
+ else {
+ // Not allowed, throw an exception.
+ throw err;
+ }
+ },
+
+ getPrompt: function(win, iid) {
+ // It is possible for some object to get a prompt without passing
+ // valid reference of window, like nsNSSComponent. In such case, we
+ // should just fall back to the native prompt service
+ if (!win)
+ return this._getNativePromptIfAllowed(win, iid, Cr.NS_ERROR_INVALID_ARG);
+
+ if (iid.number != Ci.nsIPrompt.number &&
+ iid.number != Ci.nsIAuthPrompt2.number) {
+ debug("We don't recognize the requested IID (" + iid + ", " +
+ "allowed IID: " +
+ "nsIPrompt=" + Ci.nsIPrompt + ", " +
+ "nsIAuthPrompt2=" + Ci.nsIAuthPrompt2 + ")");
+ return this._getNativePromptIfAllowed(win, iid, Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ // Try to find a BrowserElementChild for the window.
+ let browserElementChild =
+ BrowserElementPromptService.getBrowserElementChildForWindow(win);
+
+ if (iid.number === Ci.nsIAuthPrompt2.number) {
+ debug("Caller requests an instance of nsIAuthPrompt2.");
+
+ if (browserElementChild) {
+ // If we are able to get a BrowserElementChild, it means that
+ // the auth prompt is for a mozbrowser. Therefore we don't need to
+ // fall back.
+ return new BrowserElementAuthPrompt().QueryInterface(iid);
+ }
+
+ // Because nsIAuthPrompt2 is called in parent process. If caller
+ // wants nsIAuthPrompt2 and we cannot get BrowserElementchild,
+ // it doesn't mean that we should fallback. It is possible that we can
+ // get the BrowserElementParent from nsIChannel that passed to
+ // functions of nsIAuthPrompt2.
+ if (this._mayUseNativePrompt()) {
+ return new AuthPromptWrapper(
+ this._wrapped.getPrompt(win, iid),
+ new BrowserElementAuthPrompt().QueryInterface(iid))
+ .QueryInterface(iid);
+ } else {
+ // Falling back is not allowed, so we don't need wrap the
+ // BrowserElementPrompt.
+ return new BrowserElementAuthPrompt().QueryInterface(iid);
+ }
+ }
+
+ if (!browserElementChild) {
+ debug("We can't find a browserElementChild for " +
+ win + ", " + win.location);
+ return this._getNativePromptIfAllowed(win, iid, Cr.NS_ERROR_FAILURE);
+ }
+
+ debug("Returning wrapped getPrompt for " + win);
+ return new BrowserElementPrompt(win, browserElementChild)
+ .QueryInterface(iid);
+ }
+};
+
+this.BrowserElementPromptService = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+
+ _initialized: false,
+
+ _init: function() {
+ if (this._initialized) {
+ return;
+ }
+
+ // If the pref is disabled, do nothing except wait for the pref to change.
+ if (!this._browserFramesPrefEnabled()) {
+ var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ prefs.addObserver(BROWSER_FRAMES_ENABLED_PREF, this, /* ownsWeak = */ true);
+ return;
+ }
+
+ this._initialized = true;
+ this._browserElementParentMap = new WeakMap();
+
+ var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+ os.addObserver(this, "outer-window-destroyed", /* ownsWeak = */ true);
+
+ // Wrap the existing @mozilla.org/prompter;1 implementation.
+ var contractID = "@mozilla.org/prompter;1";
+ var oldCID = Cm.contractIDToCID(contractID);
+ var newCID = BrowserElementPromptFactory.prototype.classID;
+ var oldFactory = Cm.getClassObject(Cc[contractID], Ci.nsIFactory);
+
+ if (oldCID == newCID) {
+ debug("WARNING: Wrapped prompt factory is already installed!");
+ return;
+ }
+
+ Cm.unregisterFactory(oldCID, oldFactory);
+
+ var oldInstance = oldFactory.createInstance(null, Ci.nsIPromptFactory);
+ var newInstance = new BrowserElementPromptFactory(oldInstance);
+
+ var newFactory = {
+ createInstance: function(outer, iid) {
+ if (outer != null) {
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ }
+ return newInstance.QueryInterface(iid);
+ }
+ };
+ Cm.registerFactory(newCID,
+ "BrowserElementPromptService's prompter;1 wrapper",
+ contractID, newFactory);
+
+ debug("Done installing new prompt factory.");
+ },
+
+ _getOuterWindowID: function(win) {
+ return win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .outerWindowID;
+ },
+
+ _browserElementChildMap: {},
+ mapWindowToBrowserElementChild: function(win, browserElementChild) {
+ this._browserElementChildMap[this._getOuterWindowID(win)] = browserElementChild;
+ },
+ unmapWindowToBrowserElementChild: function(win) {
+ delete this._browserElementChildMap[this._getOuterWindowID(win)];
+ },
+
+ getBrowserElementChildForWindow: function(win) {
+ // We only have a mapping for <iframe mozbrowser>s, not their inner
+ // <iframes>, so we look up win.top below. window.top (when called from
+ // script) respects <iframe mozbrowser> boundaries.
+ return this._browserElementChildMap[this._getOuterWindowID(win.top)];
+ },
+
+ mapFrameToBrowserElementParent: function(frame, browserElementParent) {
+ this._browserElementParentMap.set(frame, browserElementParent);
+ },
+
+ getBrowserElementParentForFrame: function(frame) {
+ return this._browserElementParentMap.get(frame);
+ },
+
+ _observeOuterWindowDestroyed: function(outerWindowID) {
+ let id = outerWindowID.QueryInterface(Ci.nsISupportsPRUint64).data;
+ debug("observeOuterWindowDestroyed " + id);
+ delete this._browserElementChildMap[outerWindowID.data];
+ },
+
+ _browserFramesPrefEnabled: function() {
+ var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ try {
+ return prefs.getBoolPref(BROWSER_FRAMES_ENABLED_PREF);
+ }
+ catch(e) {
+ return false;
+ }
+ },
+
+ observe: function(subject, topic, data) {
+ switch(topic) {
+ case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
+ if (data == BROWSER_FRAMES_ENABLED_PREF) {
+ this._init();
+ }
+ break;
+ case "outer-window-destroyed":
+ this._observeOuterWindowDestroyed(subject);
+ break;
+ default:
+ debug("Observed unexpected topic " + topic);
+ }
+ }
+};
+
+BrowserElementPromptService._init();