toolkit/components/prompts/src/CommonDialog.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/components/prompts/src/CommonDialog.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,345 @@
     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
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +this.EXPORTED_SYMBOLS = ["CommonDialog"];
     1.9 +
    1.10 +const Ci = Components.interfaces;
    1.11 +const Cr = Components.results;
    1.12 +const Cc = Components.classes;
    1.13 +const Cu = Components.utils;
    1.14 +
    1.15 +Cu.import("resource://gre/modules/Services.jsm");
    1.16 +
    1.17 +
    1.18 +this.CommonDialog = function CommonDialog(args, ui) {
    1.19 +    this.args = args;
    1.20 +    this.ui   = ui;
    1.21 +}
    1.22 +
    1.23 +CommonDialog.prototype = {
    1.24 +    args : null,
    1.25 +    ui   : null,
    1.26 +
    1.27 +    hasInputField : true,
    1.28 +    numButtons    : undefined,
    1.29 +    iconClass     : undefined,
    1.30 +    soundID       : undefined,
    1.31 +    focusTimer    : null,
    1.32 +
    1.33 +    onLoad : function(xulDialog) {
    1.34 +        switch (this.args.promptType) {
    1.35 +          case "alert":
    1.36 +          case "alertCheck":
    1.37 +            this.hasInputField = false;
    1.38 +            this.numButtons    = 1;
    1.39 +            this.iconClass     = ["alert-icon"];
    1.40 +            this.soundID       = Ci.nsISound.EVENT_ALERT_DIALOG_OPEN;
    1.41 +            break;
    1.42 +          case "confirmCheck":
    1.43 +          case "confirm":
    1.44 +            this.hasInputField = false;
    1.45 +            this.numButtons    = 2;
    1.46 +            this.iconClass     = ["question-icon"];
    1.47 +            this.soundID       = Ci.nsISound.EVENT_CONFIRM_DIALOG_OPEN;
    1.48 +            break;
    1.49 +          case "confirmEx":
    1.50 +            var numButtons = 0;
    1.51 +            if (this.args.button0Label)
    1.52 +                numButtons++;
    1.53 +            if (this.args.button1Label)
    1.54 +                numButtons++;
    1.55 +            if (this.args.button2Label)
    1.56 +                numButtons++;
    1.57 +            if (this.args.button3Label)
    1.58 +                numButtons++;
    1.59 +            if (numButtons == 0)
    1.60 +                throw "A dialog with no buttons? Can not haz.";
    1.61 +            this.numButtons    = numButtons;
    1.62 +            this.hasInputField = false;
    1.63 +            this.iconClass     = ["question-icon"];
    1.64 +            this.soundID       = Ci.nsISound.EVENT_CONFIRM_DIALOG_OPEN;
    1.65 +            break;
    1.66 +          case "prompt":
    1.67 +            this.numButtons = 2;
    1.68 +            this.iconClass  = ["question-icon"];
    1.69 +            this.soundID    = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
    1.70 +            this.initTextbox("login", this.args.value);
    1.71 +            // Clear the label, since this isn't really a username prompt.
    1.72 +            this.ui.loginLabel.setAttribute("value", "");
    1.73 +            break;
    1.74 +          case "promptUserAndPass":
    1.75 +            this.numButtons = 2;
    1.76 +            this.iconClass  = ["authentication-icon", "question-icon"];
    1.77 +            this.soundID    = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
    1.78 +            this.initTextbox("login",     this.args.user);
    1.79 +            this.initTextbox("password1", this.args.pass);
    1.80 +            break;
    1.81 +          case "promptPassword":
    1.82 +            this.numButtons = 2;
    1.83 +            this.iconClass  = ["authentication-icon", "question-icon"];
    1.84 +            this.soundID    = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
    1.85 +            this.initTextbox("password1", this.args.pass);
    1.86 +            // Clear the label, since the message presumably indicates its purpose.
    1.87 +            this.ui.password1Label.setAttribute("value", "");
    1.88 +            break;
    1.89 +          default:
    1.90 +            Cu.reportError("commonDialog opened for unknown type: " + this.args.promptType);
    1.91 +            throw "unknown dialog type";
    1.92 +        }
    1.93 +
    1.94 +        // set the document title
    1.95 +        let title = this.args.title;
    1.96 +        // OS X doesn't have a title on modal dialogs, this is hidden on other platforms.
    1.97 +        let infoTitle = this.ui.infoTitle;
    1.98 +        infoTitle.appendChild(infoTitle.ownerDocument.createTextNode(title));
    1.99 +        if (xulDialog)
   1.100 +            xulDialog.ownerDocument.title = title;
   1.101 +
   1.102 +        // Set button labels and visibility
   1.103 +        //
   1.104 +        // This assumes that button0 defaults to a visible "ok" button, and
   1.105 +        // button1 defaults to a visible "cancel" button. The other 2 buttons
   1.106 +        // have no default labels (and are hidden).
   1.107 +        switch (this.numButtons) {
   1.108 +          case 4:
   1.109 +            this.setLabelForNode(this.ui.button3, this.args.button3Label);
   1.110 +            this.ui.button3.hidden = false;
   1.111 +            // fall through
   1.112 +          case 3:
   1.113 +            this.setLabelForNode(this.ui.button2, this.args.button2Label);
   1.114 +            this.ui.button2.hidden = false;
   1.115 +            // fall through
   1.116 +          case 2:
   1.117 +            // Defaults to a visible "cancel" button
   1.118 +            if (this.args.button1Label)
   1.119 +                this.setLabelForNode(this.ui.button1, this.args.button1Label);
   1.120 +            break;
   1.121 +
   1.122 +          case 1:
   1.123 +            this.ui.button1.hidden = true;
   1.124 +            break;
   1.125 +        }
   1.126 +        // Defaults to a visible "ok" button
   1.127 +        if (this.args.button0Label)
   1.128 +            this.setLabelForNode(this.ui.button0, this.args.button0Label);
   1.129 +
   1.130 +        // display the main text
   1.131 +        // Bug 317334 - crop string length as a workaround.
   1.132 +        let croppedMessage = this.args.text.substr(0, 10000);
   1.133 +        let infoBody = this.ui.infoBody;
   1.134 +        infoBody.appendChild(infoBody.ownerDocument.createTextNode(croppedMessage));
   1.135 +
   1.136 +        let label = this.args.checkLabel;
   1.137 +        if (label) {
   1.138 +            // Only show the checkbox if label has a value.
   1.139 +            this.ui.checkboxContainer.hidden = false;
   1.140 +            this.setLabelForNode(this.ui.checkbox, label);
   1.141 +            this.ui.checkbox.checked = this.args.checked;
   1.142 +        }
   1.143 +
   1.144 +        // set the icon
   1.145 +        let icon = this.ui.infoIcon;
   1.146 +        if (icon)
   1.147 +            this.iconClass.forEach(function(el,idx,arr) icon.classList.add(el));
   1.148 +
   1.149 +        // set default result to cancelled
   1.150 +        this.args.ok = false;
   1.151 +        this.args.buttonNumClicked = 1;
   1.152 +
   1.153 +
   1.154 +        // Set the default button
   1.155 +        let b = (this.args.defaultButtonNum || 0);
   1.156 +        let button = this.ui["button" + b];
   1.157 +
   1.158 +        if (xulDialog)
   1.159 +            xulDialog.defaultButton = ['accept', 'cancel', 'extra1', 'extra2'][b];
   1.160 +        else
   1.161 +            button.setAttribute("default", "true");
   1.162 +
   1.163 +        // Set default focus / selection.
   1.164 +        this.setDefaultFocus(true);
   1.165 +
   1.166 +        if (this.args.enableDelay) {
   1.167 +            this.setButtonsEnabledState(false);
   1.168 +            // Use a longer, pref-controlled delay when the dialog is first opened.
   1.169 +            let delayTime = Services.prefs.getIntPref("security.dialog_enable_delay");
   1.170 +            this.startOnFocusDelay(delayTime);
   1.171 +            let self = this;
   1.172 +            this.ui.focusTarget.addEventListener("blur",  function(e) { self.onBlur(e);  }, false);
   1.173 +            this.ui.focusTarget.addEventListener("focus", function(e) { self.onFocus(e); }, false);
   1.174 +        }
   1.175 +
   1.176 +        // Play a sound (unless we're tab-modal -- don't want those to feel like OS prompts).
   1.177 +        try {
   1.178 +            if (xulDialog && this.soundID) {
   1.179 +                Cc["@mozilla.org/sound;1"].
   1.180 +                createInstance(Ci.nsISound).
   1.181 +                playEventSound(this.soundID);
   1.182 +            }
   1.183 +        } catch (e) {
   1.184 +            Cu.reportError("Couldn't play common dialog event sound: " + e);
   1.185 +        }
   1.186 +
   1.187 +        let topic = "common-dialog-loaded";
   1.188 +        if (!xulDialog)
   1.189 +            topic = "tabmodal-dialog-loaded";
   1.190 +        Services.obs.notifyObservers(this.ui.prompt, topic, null);
   1.191 +    },
   1.192 +
   1.193 +    setLabelForNode: function(aNode, aLabel) {
   1.194 +        // This is for labels which may contain embedded access keys.
   1.195 +        // If we end in (&X) where X represents the access key, optionally preceded
   1.196 +        // by spaces and/or followed by the ':' character, store the access key and
   1.197 +        // remove the access key placeholder + leading spaces from the label.
   1.198 +        // Otherwise a character preceded by one but not two &s is the access key.
   1.199 +        // Store it and remove the &.
   1.200 +
   1.201 +        // Note that if you change the following code, see the comment of
   1.202 +        // nsTextBoxFrame::UpdateAccessTitle.
   1.203 +        var accessKey = null;
   1.204 +        if (/ *\(\&([^&])\)(:)?$/.test(aLabel)) {
   1.205 +            aLabel = RegExp.leftContext + RegExp.$2;
   1.206 +            accessKey = RegExp.$1;
   1.207 +        } else if (/^(.*[^&])?\&(([^&]).*$)/.test(aLabel)) {
   1.208 +            aLabel = RegExp.$1 + RegExp.$2;
   1.209 +            accessKey = RegExp.$3;
   1.210 +        }
   1.211 +
   1.212 +        // && is the magic sequence to embed an & in your label.
   1.213 +        aLabel = aLabel.replace(/\&\&/g, "&");
   1.214 +        aNode.label = aLabel;
   1.215 +
   1.216 +        // XXXjag bug 325251
   1.217 +        // Need to set this after aNode.setAttribute("value", aLabel);
   1.218 +        if (accessKey)
   1.219 +            aNode.accessKey = accessKey;
   1.220 +    },
   1.221 +
   1.222 +
   1.223 +    initTextbox : function (aName, aValue) {
   1.224 +        this.ui[aName + "Container"].hidden = false;
   1.225 +        this.ui[aName + "Textbox"].setAttribute("value",
   1.226 +                                                aValue !== null ? aValue : "");
   1.227 +    },
   1.228 +
   1.229 +    setButtonsEnabledState : function(enabled) {
   1.230 +        this.ui.button0.disabled = !enabled;
   1.231 +        // button1 (cancel) remains enabled.
   1.232 +        this.ui.button2.disabled = !enabled;
   1.233 +        this.ui.button3.disabled = !enabled;
   1.234 +    },
   1.235 +
   1.236 +    onBlur : function (aEvent) {
   1.237 +        if (aEvent.target != this.ui.focusTarget)
   1.238 +            return;
   1.239 +        this.setButtonsEnabledState(false);
   1.240 +
   1.241 +        // If we blur while waiting to enable the buttons, just cancel the
   1.242 +        // timer to ensure the delay doesn't fire while not focused.
   1.243 +        if (this.focusTimer) {
   1.244 +            this.focusTimer.cancel();
   1.245 +            this.focusTimer = null;
   1.246 +        }
   1.247 +    },
   1.248 +
   1.249 +    onFocus : function (aEvent) {
   1.250 +        if (aEvent.target != this.ui.focusTarget)
   1.251 +            return;
   1.252 +        this.startOnFocusDelay();
   1.253 +    },
   1.254 +
   1.255 +    startOnFocusDelay : function(delayTime) {
   1.256 +        // Shouldn't already have a timer, but just in case...
   1.257 +        if (this.focusTimer)
   1.258 +            return;
   1.259 +        // If no delay specified, use 250ms. (This is the normal case for when
   1.260 +        // after the dialog has been opened and focus shifts.)
   1.261 +        if (!delayTime)
   1.262 +            delayTime = 250;
   1.263 +        let self = this;
   1.264 +        this.focusTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   1.265 +        this.focusTimer.initWithCallback(function() { self.onFocusTimeout(); },
   1.266 +                                         delayTime, Ci.nsITimer.TYPE_ONE_SHOT);
   1.267 +    },
   1.268 +
   1.269 +    onFocusTimeout : function() {
   1.270 +        this.focusTimer = null;
   1.271 +        this.setButtonsEnabledState(true);
   1.272 +    },
   1.273 +
   1.274 +    setDefaultFocus : function(isInitialLoad) {
   1.275 +        let b = (this.args.defaultButtonNum || 0);
   1.276 +        let button = this.ui["button" + b];
   1.277 +
   1.278 +        if (!this.hasInputField) {
   1.279 +            let isOSX = ("nsILocalFileMac" in Components.interfaces);
   1.280 +            if (isOSX)
   1.281 +                this.ui.infoBody.focus();
   1.282 +            else
   1.283 +                button.focus();
   1.284 +        } else {
   1.285 +            // When the prompt is initialized, focus and select the textbox
   1.286 +            // contents. Afterwards, only focus the textbox.
   1.287 +            if (this.args.promptType == "promptPassword") {
   1.288 +                if (isInitialLoad)
   1.289 +                    this.ui.password1Textbox.select();
   1.290 +                else
   1.291 +                    this.ui.password1Textbox.focus();
   1.292 +            } else {
   1.293 +                if (isInitialLoad)
   1.294 +                    this.ui.loginTextbox.select();
   1.295 +                else
   1.296 +                    this.ui.loginTextbox.focus();
   1.297 +            }
   1.298 +        }
   1.299 +    },
   1.300 +
   1.301 +    onCheckbox : function() {
   1.302 +        this.args.checked = this.ui.checkbox.checked;
   1.303 +    },
   1.304 +
   1.305 +    onButton0 : function() {
   1.306 +        this.args.promptActive = false;
   1.307 +        this.args.ok = true;
   1.308 +        this.args.buttonNumClicked = 0;
   1.309 +
   1.310 +        let username = this.ui.loginTextbox.value;
   1.311 +        let password = this.ui.password1Textbox.value;
   1.312 +
   1.313 +        // Return textfield values
   1.314 +        switch (this.args.promptType) {
   1.315 +          case "prompt":
   1.316 +            this.args.value = username;
   1.317 +            break;
   1.318 +          case "promptUserAndPass":
   1.319 +            this.args.user = username;
   1.320 +            this.args.pass = password;
   1.321 +            break;
   1.322 +          case "promptPassword":
   1.323 +            this.args.pass = password;
   1.324 +            break;
   1.325 +        }
   1.326 +    },
   1.327 +
   1.328 +    onButton1 : function() {
   1.329 +        this.args.promptActive = false;
   1.330 +        this.args.buttonNumClicked = 1;
   1.331 +    },
   1.332 +
   1.333 +    onButton2 : function() {
   1.334 +        this.args.promptActive = false;
   1.335 +        this.args.buttonNumClicked = 2;
   1.336 +    },
   1.337 +
   1.338 +    onButton3 : function() {
   1.339 +        this.args.promptActive = false;
   1.340 +        this.args.buttonNumClicked = 3;
   1.341 +    },
   1.342 +
   1.343 +    abortPrompt : function() {
   1.344 +        this.args.promptActive = false;
   1.345 +        this.args.promptAborted = true;
   1.346 +    },
   1.347 +
   1.348 +};

mercurial