diff options
Diffstat (limited to 'toolkit/components/prompts/src/SharedPromptUtils.jsm')
-rw-r--r-- | toolkit/components/prompts/src/SharedPromptUtils.jsm | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/toolkit/components/prompts/src/SharedPromptUtils.jsm b/toolkit/components/prompts/src/SharedPromptUtils.jsm new file mode 100644 index 000000000..b27096ac2 --- /dev/null +++ b/toolkit/components/prompts/src/SharedPromptUtils.jsm @@ -0,0 +1,157 @@ +this.EXPORTED_SYMBOLS = [ "PromptUtils", "EnableDelayHelper" ]; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm"); + +this.PromptUtils = { + // Fire a dialog open/close event. Used by tabbrowser to focus the + // tab which is triggering a prompt. + // For remote dialogs, we pass in a different DOM window and a separate + // target. If the caller doesn't pass in the target, then we'll simply use + // the passed-in DOM window. + // The detail may contain information about the principal on which the + // prompt is triggered, as well as whether or not this is a tabprompt + // (ie tabmodal alert/prompt/confirm and friends) + fireDialogEvent : function (domWin, eventName, maybeTarget, detail) { + let target = maybeTarget || domWin; + let eventOptions = {cancelable: true, bubbles: true}; + if (detail) { + eventOptions.detail = detail; + } + let event = new domWin.CustomEvent(eventName, eventOptions); + let winUtils = domWin.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + winUtils.dispatchEventToChromeOnly(target, event); + }, + + objectToPropBag : function (obj) { + let bag = Cc["@mozilla.org/hash-property-bag;1"]. + createInstance(Ci.nsIWritablePropertyBag2); + bag.QueryInterface(Ci.nsIWritablePropertyBag); + + for (let propName in obj) + bag.setProperty(propName, obj[propName]); + + return bag; + }, + + propBagToObject : function (propBag, obj) { + // Here we iterate over the object's original properties, not the bag + // (ie, the prompt can't return more/different properties than were + // passed in). This just helps ensure that the caller provides default + // values, lest the prompt forget to set them. + for (let propName in obj) + obj[propName] = propBag.getProperty(propName); + }, +}; + +/** + * This helper handles the enabling/disabling of dialogs that might + * be subject to fast-clicking attacks. It handles the initial delayed + * enabling of the dialog, as well as disabling it on blur and reapplying + * the delay when the dialog regains focus. + * + * @param enableDialog A custom function to be called when the dialog + * is to be enabled. + * @param diableDialog A custom function to be called when the dialog + * is to be disabled. + * @param focusTarget The window used to watch focus/blur events. + */ +this.EnableDelayHelper = function({enableDialog, disableDialog, focusTarget}) { + this.enableDialog = makeSafe(enableDialog); + this.disableDialog = makeSafe(disableDialog); + this.focusTarget = focusTarget; + + this.disableDialog(); + + this.focusTarget.addEventListener("blur", this, false); + this.focusTarget.addEventListener("focus", this, false); + this.focusTarget.document.addEventListener("unload", this, false); + + this.startOnFocusDelay(); +}; + +this.EnableDelayHelper.prototype = { + get delayTime() { + return Services.prefs.getIntPref("security.dialog_enable_delay"); + }, + + handleEvent : function(event) { + if (event.target != this.focusTarget && + event.target != this.focusTarget.document) + return; + + switch (event.type) { + case "blur": + this.onBlur(); + break; + + case "focus": + this.onFocus(); + break; + + case "unload": + this.onUnload(); + break; + } + }, + + onBlur : function () { + this.disableDialog(); + // If we blur while waiting to enable the buttons, just cancel the + // timer to ensure the delay doesn't fire while not focused. + if (this._focusTimer) { + this._focusTimer.cancel(); + this._focusTimer = null; + } + }, + + onFocus : function () { + this.startOnFocusDelay(); + }, + + onUnload: function() { + this.focusTarget.removeEventListener("blur", this, false); + this.focusTarget.removeEventListener("focus", this, false); + this.focusTarget.document.removeEventListener("unload", this, false); + + if (this._focusTimer) { + this._focusTimer.cancel(); + this._focusTimer = null; + } + + this.focusTarget = this.enableDialog = this.disableDialog = null; + }, + + startOnFocusDelay : function() { + if (this._focusTimer) + return; + + this._focusTimer = Cc["@mozilla.org/timer;1"] + .createInstance(Ci.nsITimer); + this._focusTimer.initWithCallback( + () => { this.onFocusTimeout(); }, + this.delayTime, + Ci.nsITimer.TYPE_ONE_SHOT + ); + }, + + onFocusTimeout : function() { + this._focusTimer = null; + this.enableDialog(); + }, +}; + +function makeSafe(fn) { + return function () { + // The dialog could be gone by now (if the user closed it), + // which makes it likely that the given fn might throw. + try { + fn(); + } catch (e) { } + }; +} |