Source code

Revision control

Copy as Markdown

Other Tools

/* 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/. */
export var 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(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.windowUtils;
winUtils.dispatchEventToChromeOnly(target, event);
},
objectToPropBag(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(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.
*/
export var EnableDelayHelper = function ({
enableDialog,
disableDialog,
focusTarget,
}) {
this.enableDialog = makeSafe(enableDialog);
this.disableDialog = makeSafe(disableDialog);
this.focusTarget = focusTarget;
this.disableDialog();
this.focusTarget.addEventListener("blur", this);
this.focusTarget.addEventListener("focus", this);
// While the user key-repeats, we want to renew the timer until keyup:
this.focusTarget.addEventListener("keyup", this, true);
this.focusTarget.addEventListener("keydown", this, true);
this.focusTarget.document.addEventListener("unload", this);
// If we're not part of the active window, don't even start the timer yet.
let topWin = focusTarget.browsingContext.top.window;
if (topWin != Services.focus.activeWindow) {
return;
}
this.startOnFocusDelay();
};
EnableDelayHelper.prototype = {
get delayTime() {
return Services.prefs.getIntPref("security.dialog_enable_delay");
},
handleEvent(event) {
if (
!event.type.startsWith("key") &&
event.target != this.focusTarget &&
event.target != this.focusTarget.document
) {
return;
}
switch (event.type) {
case "keyup":
// As soon as any key goes up, we can stop treating keypresses
// as indicative of key-repeating that should prolong the timer.
this.focusTarget.removeEventListener("keyup", this, true);
this.focusTarget.removeEventListener("keydown", this, true);
break;
case "keydown":
// Renew timer for repeating keydowns:
if (this._focusTimer) {
this._focusTimer.cancel();
this._focusTimer = null;
this.startOnFocusDelay();
event.preventDefault();
}
break;
case "blur":
this.onBlur();
break;
case "focus":
this.onFocus();
break;
case "unload":
this.onUnload();
break;
}
},
onBlur() {
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() {
this.startOnFocusDelay();
},
onUnload() {
this.focusTarget.removeEventListener("blur", this);
this.focusTarget.removeEventListener("focus", this);
this.focusTarget.removeEventListener("keyup", this, true);
this.focusTarget.removeEventListener("keydown", this, true);
this.focusTarget.document.removeEventListener("unload", this);
if (this._focusTimer) {
this._focusTimer.cancel();
this._focusTimer = null;
}
this.focusTarget = this.enableDialog = this.disableDialog = null;
},
startOnFocusDelay() {
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() {
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) {}
};
}