summaryrefslogtreecommitdiffstats
path: root/toolkit/components/prompts
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/prompts')
-rw-r--r--toolkit/components/prompts/content/commonDialog.css22
-rw-r--r--toolkit/components/prompts/content/commonDialog.js62
-rw-r--r--toolkit/components/prompts/content/commonDialog.xul97
-rw-r--r--toolkit/components/prompts/content/selectDialog.js67
-rw-r--r--toolkit/components/prompts/content/selectDialog.xul22
-rw-r--r--toolkit/components/prompts/content/tabprompts.css35
-rw-r--r--toolkit/components/prompts/content/tabprompts.xml352
-rw-r--r--toolkit/components/prompts/jar.mn12
-rw-r--r--toolkit/components/prompts/moz.build11
-rw-r--r--toolkit/components/prompts/src/CommonDialog.jsm308
-rw-r--r--toolkit/components/prompts/src/SharedPromptUtils.jsm157
-rw-r--r--toolkit/components/prompts/src/moz.build16
-rw-r--r--toolkit/components/prompts/src/nsPrompter.js958
-rw-r--r--toolkit/components/prompts/src/nsPrompter.manifest6
-rw-r--r--toolkit/components/prompts/test/.eslintrc.js7
-rw-r--r--toolkit/components/prompts/test/bug619644_inner.html7
-rw-r--r--toolkit/components/prompts/test/bug625187_iframe.html16
-rw-r--r--toolkit/components/prompts/test/chromeScript.js241
-rw-r--r--toolkit/components/prompts/test/mochitest.ini19
-rw-r--r--toolkit/components/prompts/test/prompt_common.js158
-rw-r--r--toolkit/components/prompts/test/test_bug619644.html76
-rw-r--r--toolkit/components/prompts/test/test_bug620145.html105
-rw-r--r--toolkit/components/prompts/test/test_dom_prompts.html208
-rw-r--r--toolkit/components/prompts/test/test_modal_prompts.html1184
-rw-r--r--toolkit/components/prompts/test/test_modal_select.html146
-rw-r--r--toolkit/components/prompts/test/test_subresources_prompts.html202
26 files changed, 4494 insertions, 0 deletions
diff --git a/toolkit/components/prompts/content/commonDialog.css b/toolkit/components/prompts/content/commonDialog.css
new file mode 100644
index 000000000..89f88db7a
--- /dev/null
+++ b/toolkit/components/prompts/content/commonDialog.css
@@ -0,0 +1,22 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */
+
+#infoContainer {
+ max-width: 45em;
+}
+
+#info\.body {
+ -moz-user-focus: normal;
+ -moz-user-select: text;
+ cursor: text !important;
+ white-space: pre-wrap;
+ unicode-bidi: plaintext;
+}
+
+#loginLabel, #password1Label {
+ text-align: right;
+}
+
diff --git a/toolkit/components/prompts/content/commonDialog.js b/toolkit/components/prompts/content/commonDialog.js
new file mode 100644
index 000000000..ef4686654
--- /dev/null
+++ b/toolkit/components/prompts/content/commonDialog.js
@@ -0,0 +1,62 @@
+/* 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/. */
+
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cc = Components.classes;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/CommonDialog.jsm");
+
+var propBag, args, Dialog;
+
+function commonDialogOnLoad() {
+ propBag = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2)
+ .QueryInterface(Ci.nsIWritablePropertyBag);
+ // Convert to a JS object
+ args = {};
+ let propEnum = propBag.enumerator;
+ while (propEnum.hasMoreElements()) {
+ let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
+ args[prop.name] = prop.value;
+ }
+
+ let dialog = document.documentElement;
+
+ let ui = {
+ prompt : window,
+ loginContainer : document.getElementById("loginContainer"),
+ loginTextbox : document.getElementById("loginTextbox"),
+ loginLabel : document.getElementById("loginLabel"),
+ password1Container : document.getElementById("password1Container"),
+ password1Textbox : document.getElementById("password1Textbox"),
+ password1Label : document.getElementById("password1Label"),
+ infoBody : document.getElementById("info.body"),
+ infoTitle : document.getElementById("info.title"),
+ infoIcon : document.getElementById("info.icon"),
+ checkbox : document.getElementById("checkbox"),
+ checkboxContainer : document.getElementById("checkboxContainer"),
+ button3 : dialog.getButton("extra2"),
+ button2 : dialog.getButton("extra1"),
+ button1 : dialog.getButton("cancel"),
+ button0 : dialog.getButton("accept"),
+ focusTarget : window,
+ };
+
+ // limit the dialog to the screen width
+ document.getElementById("filler").maxWidth = screen.availWidth;
+
+ Dialog = new CommonDialog(args, ui);
+ Dialog.onLoad(dialog);
+ // resize the window to the content
+ window.sizeToContent();
+ window.getAttention();
+}
+
+function commonDialogOnUnload() {
+ // Convert args back into property bag
+ for (let propName in args)
+ propBag.setProperty(propName, args[propName]);
+}
diff --git a/toolkit/components/prompts/content/commonDialog.xul b/toolkit/components/prompts/content/commonDialog.xul
new file mode 100644
index 000000000..990b26586
--- /dev/null
+++ b/toolkit/components/prompts/content/commonDialog.xul
@@ -0,0 +1,97 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://global/content/commonDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://global/skin/commonDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://global/locale/commonDialog.dtd">
+
+<dialog id="commonDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ aria-describedby="info.body"
+ onunload="commonDialogOnUnload();"
+ ondialogaccept="Dialog.onButton0(); return true;"
+ ondialogcancel="Dialog.onButton1(); return true;"
+ ondialogextra1="Dialog.onButton2(); window.close();"
+ ondialogextra2="Dialog.onButton3(); window.close();"
+ buttonpack="center">
+
+ <script type="application/javascript" src="chrome://global/content/commonDialog.js"/>
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript">
+ document.addEventListener("DOMContentLoaded", function() {
+ commonDialogOnLoad();
+ });
+ </script>
+
+ <commandset id="selectEditMenuItems">
+ <command id="cmd_copy" oncommand="goDoCommand('cmd_copy')" disabled="true"/>
+ <command id="cmd_selectAll" oncommand="goDoCommand('cmd_selectAll')"/>
+ </commandset>
+
+ <popupset id="contentAreaContextSet">
+ <menupopup id="contentAreaContextMenu"
+ onpopupshowing="goUpdateCommand('cmd_copy')">
+ <menuitem id="context-copy"
+ label="&copyCmd.label;"
+ accesskey="&copyCmd.accesskey;"
+ command="cmd_copy"
+ disabled="true"/>
+ <menuitem id="context-selectall"
+ label="&selectAllCmd.label;"
+ accesskey="&selectAllCmd.accesskey;"
+ command="cmd_selectAll"/>
+ </menupopup>
+ </popupset>
+
+ <hbox id="filler" style="min-width: 0%;">
+ <spacer style="width: 29em;"/>
+ </hbox>
+
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+
+ <rows>
+ <row>
+ <hbox id="iconContainer" align="start">
+ <image id="info.icon" class="spaced"/>
+ </hbox>
+ <vbox id="infoContainer"
+#ifndef XP_MACOSX
+ pack="center"
+#endif
+ >
+ <!-- Only shown on OS X, since it has no dialog title -->
+ <description id="info.title"
+#ifndef XP_MACOSX
+ hidden="true"
+#else
+ style="margin-bottom: 1em"
+#endif
+ />
+ <description id="info.body" context="contentAreaContextMenu" noinitialfocus="true"/>
+ </vbox>
+ </row>
+ <row id="loginContainer" hidden="true" align="center">
+ <label id="loginLabel" value="&editfield0.label;" control="loginTextbox"/>
+ <textbox id="loginTextbox"/>
+ </row>
+ <row id ="password1Container" hidden="true" align="center">
+ <label id="password1Label" value="&editfield1.label;" control="password1Textbox"/>
+ <textbox type="password" id="password1Textbox"/>
+ </row>
+ <row id="checkboxContainer" hidden="true">
+ <spacer/>
+ <checkbox id="checkbox" oncommand="Dialog.onCheckbox()"/>
+ </row>
+ </rows>
+ </grid>
+
+</dialog>
diff --git a/toolkit/components/prompts/content/selectDialog.js b/toolkit/components/prompts/content/selectDialog.js
new file mode 100644
index 000000000..7628dc8d9
--- /dev/null
+++ b/toolkit/components/prompts/content/selectDialog.js
@@ -0,0 +1,67 @@
+/* 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/. */
+
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cc = Components.classes;
+var Cu = Components.utils;
+
+var gArgs, listBox;
+
+function dialogOnLoad() {
+ gArgs = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2)
+ .QueryInterface(Ci.nsIWritablePropertyBag);
+
+ let promptType = gArgs.getProperty("promptType");
+ if (promptType != "select") {
+ Cu.reportError("selectDialog opened for unknown type: " + promptType);
+ window.close();
+ }
+
+ // Default to canceled.
+ gArgs.setProperty("ok", false);
+
+ document.title = gArgs.getProperty("title");
+
+ let text = gArgs.getProperty("text");
+ document.getElementById("info.txt").setAttribute("value", text);
+
+ let items = gArgs.getProperty("list");
+ listBox = document.getElementById("list");
+
+ for (let i = 0; i < items.length; i++) {
+ let str = items[i];
+ if (str == "")
+ str = "<>";
+ listBox.appendItem(str);
+ listBox.getItemAtIndex(i).addEventListener("dblclick", dialogDoubleClick, false);
+ }
+ listBox.selectedIndex = 0;
+ listBox.focus();
+
+ // resize the window to the content
+ window.sizeToContent();
+
+ // Move to the right location
+ moveToAlertPosition();
+ centerWindowOnScreen();
+
+ // play sound
+ try {
+ Cc["@mozilla.org/sound;1"].
+ createInstance(Ci.nsISound).
+ playEventSound(Ci.nsISound.EVENT_SELECT_DIALOG_OPEN);
+ } catch (e) { }
+}
+
+function dialogOK() {
+ gArgs.setProperty("selected", listBox.selectedIndex);
+ gArgs.setProperty("ok", true);
+ return true;
+}
+
+function dialogDoubleClick() {
+ dialogOK();
+ window.close();
+}
diff --git a/toolkit/components/prompts/content/selectDialog.xul b/toolkit/components/prompts/content/selectDialog.xul
new file mode 100644
index 000000000..9b72bcfb0
--- /dev/null
+++ b/toolkit/components/prompts/content/selectDialog.xul
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<!DOCTYPE dialog SYSTEM "chrome://global/locale/commonDialog.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="dialogOnLoad()"
+ ondialogaccept="return dialogOK();">
+
+ <script type="application/javascript" src="chrome://global/content/selectDialog.js" />
+ <keyset id="dialogKeys"/>
+ <vbox style="width: 24em;margin: 5px;">
+ <label id="info.txt"/>
+ <vbox>
+ <listbox id="list" rows="4" flex="1"/>
+ </vbox>
+ </vbox>
+</dialog>
diff --git a/toolkit/components/prompts/content/tabprompts.css b/toolkit/components/prompts/content/tabprompts.css
new file mode 100644
index 000000000..c4b0f7593
--- /dev/null
+++ b/toolkit/components/prompts/content/tabprompts.css
@@ -0,0 +1,35 @@
+/* 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/. */
+
+/* Tab Modal Prompt boxes */
+tabmodalprompt {
+ width: 100%;
+ height: 100%;
+ -moz-box-pack: center;
+ -moz-box-orient: vertical;
+}
+
+.mainContainer {
+ min-width: 20em;
+ min-height: 12em;
+ -moz-user-focus: normal;
+}
+
+.info\.title {
+ margin-bottom: 1em !important;
+ font-weight: bold;
+}
+
+.info\.body {
+ margin: 0 !important;
+ -moz-user-focus: normal;
+ -moz-user-select: text;
+ cursor: text !important;
+ white-space: pre-wrap;
+ unicode-bidi: plaintext;
+}
+
+label[value=""] {
+ visibility: collapse;
+}
diff --git a/toolkit/components/prompts/content/tabprompts.xml b/toolkit/components/prompts/content/tabprompts.xml
new file mode 100644
index 000000000..07c6c8efb
--- /dev/null
+++ b/toolkit/components/prompts/content/tabprompts.xml
@@ -0,0 +1,352 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<!DOCTYPE bindings [
+<!ENTITY % commonDialogDTD SYSTEM "chrome://global/locale/commonDialog.dtd">
+<!ENTITY % dialogOverlayDTD SYSTEM "chrome://global/locale/dialogOverlay.dtd">
+%commonDialogDTD;
+%dialogOverlayDTD;
+]>
+
+<bindings id="tabPrompts"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="tabmodalprompt">
+
+ <resources>
+ <stylesheet src="chrome://global/content/tabprompts.css"/>
+ <stylesheet src="chrome://global/skin/tabprompts.css"/>
+ </resources>
+
+ <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ role="dialog"
+ aria-describedby="info.body">
+
+ <!-- This is based on the guts of commonDialog.xul -->
+ <spacer flex="1"/>
+ <hbox pack="center">
+ <vbox anonid="mainContainer" class="mainContainer">
+ <grid class="topContainer" flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+
+ <rows>
+ <vbox anonid="infoContainer" align="center" pack="center" flex="1">
+ <description anonid="info.title" class="info.title" hidden="true" />
+ <description anonid="info.body" class="info.body"/>
+ </vbox>
+
+ <row anonid="loginContainer" hidden="true" align="center">
+ <label anonid="loginLabel" value="&editfield0.label;" control="loginTextbox"/>
+ <textbox anonid="loginTextbox"/>
+ </row>
+
+ <row anonid="password1Container" hidden="true" align="center">
+ <label anonid="password1Label" value="&editfield1.label;" control="password1Textbox"/>
+ <textbox anonid="password1Textbox" type="password"/>
+ </row>
+
+ <row anonid="checkboxContainer" hidden="true">
+ <spacer/>
+ <checkbox anonid="checkbox"/>
+ </row>
+
+ <xbl:children includes="row"/>
+ </rows>
+ </grid>
+ <xbl:children/>
+ <hbox class="buttonContainer">
+#ifdef XP_UNIX
+ <button anonid="button3" hidden="true"/>
+ <button anonid="button2" hidden="true"/>
+ <spacer anonid="buttonSpacer" flex="1"/>
+ <button anonid="button1" label="&cancelButton.label;"/>
+ <button anonid="button0" label="&okButton.label;"/>
+#else
+ <button anonid="button3" hidden="true"/>
+ <spacer anonid="buttonSpacer" flex="1"/>
+ <button anonid="button0" label="&okButton.label;"/>
+ <button anonid="button2" hidden="true"/>
+ <button anonid="button1" label="&cancelButton.label;"/>
+#endif
+ </hbox>
+ </vbox>
+ </hbox>
+ <spacer flex="2"/>
+ </xbl:content>
+
+ <implementation implements="nsIDOMEventListener">
+ <constructor>
+ <![CDATA[
+ let self = this;
+ function getElement(anonid) {
+ return document.getAnonymousElementByAttribute(self, "anonid", anonid);
+ }
+
+ this.ui = {
+ prompt : this,
+ loginContainer : getElement("loginContainer"),
+ loginTextbox : getElement("loginTextbox"),
+ loginLabel : getElement("loginLabel"),
+ password1Container : getElement("password1Container"),
+ password1Textbox : getElement("password1Textbox"),
+ password1Label : getElement("password1Label"),
+ infoBody : getElement("info.body"),
+ infoTitle : getElement("info.title"),
+ infoIcon : null,
+ checkbox : getElement("checkbox"),
+ checkboxContainer : getElement("checkboxContainer"),
+ button3 : getElement("button3"),
+ button2 : getElement("button2"),
+ button1 : getElement("button1"),
+ button0 : getElement("button0"),
+ // focusTarget (for BUTTON_DELAY_ENABLE) not yet supported
+ };
+
+ this.ui.button0.addEventListener("command", this.onButtonClick.bind(this, 0), false);
+ this.ui.button1.addEventListener("command", this.onButtonClick.bind(this, 1), false);
+ this.ui.button2.addEventListener("command", this.onButtonClick.bind(this, 2), false);
+ this.ui.button3.addEventListener("command", this.onButtonClick.bind(this, 3), false);
+ // Anonymous wrapper used here because |Dialog| doesn't exist until init() is called!
+ this.ui.checkbox.addEventListener("command", function() { self.Dialog.onCheckbox(); }, false);
+ this.isLive = false;
+ ]]>
+ </constructor>
+ <destructor>
+ <![CDATA[
+ if (this.isLive) {
+ this.abortPrompt();
+ }
+ ]]>
+ </destructor>
+
+ <field name="ui"/>
+ <field name="args"/>
+ <field name="linkedTab"/>
+ <field name="onCloseCallback"/>
+ <field name="Dialog"/>
+ <field name="isLive"/>
+ <field name="availWidth"/>
+ <field name="availHeight"/>
+ <field name="minWidth"/>
+ <field name="minHeight"/>
+
+ <method name="init">
+ <parameter name="args"/>
+ <parameter name="linkedTab"/>
+ <parameter name="onCloseCallback"/>
+ <body>
+ <![CDATA[
+ this.args = args;
+ this.linkedTab = linkedTab;
+ this.onCloseCallback = onCloseCallback;
+
+ if (args.enableDelay)
+ throw "BUTTON_DELAY_ENABLE not yet supported for tab-modal prompts";
+
+ // We need to remove the prompt when the tab or browser window is closed or
+ // the page navigates, else we never unwind the event loop and that's sad times.
+ // Remember to cleanup in shutdownPrompt()!
+ this.isLive = true;
+ window.addEventListener("resize", this, false);
+ window.addEventListener("unload", this, false);
+ linkedTab.addEventListener("TabClose", this, false);
+ // Note:
+ // nsPrompter.js or in e10s mode browser-parent.js call abortPrompt,
+ // when the domWindow, for which the prompt was created, generates
+ // a "pagehide" event.
+
+ let tmp = {};
+ Components.utils.import("resource://gre/modules/CommonDialog.jsm", tmp);
+ this.Dialog = new tmp.CommonDialog(args, this.ui);
+ this.Dialog.onLoad(null);
+
+ // Display the tabprompt title that shows the prompt origin when
+ // the prompt origin is not the same as that of the top window.
+ if (!args.showAlertOrigin)
+ this.ui.infoTitle.removeAttribute("hidden");
+
+ // TODO: should unhide buttonSpacer on Windows when there are 4 buttons.
+ // Better yet, just drop support for 4-button dialogs. (bug 609510)
+
+ this.onResize();
+ ]]>
+ </body>
+ </method>
+
+ <method name="shutdownPrompt">
+ <body>
+ <![CDATA[
+ // remove our event listeners
+ try {
+ window.removeEventListener("resize", this, false);
+ window.removeEventListener("unload", this, false);
+ this.linkedTab.removeEventListener("TabClose", this, false);
+ } catch (e) { }
+ this.isLive = false;
+ // invoke callback
+ this.onCloseCallback();
+ ]]>
+ </body>
+ </method>
+
+ <method name="abortPrompt">
+ <body>
+ <![CDATA[
+ // Called from other code when the page changes.
+ this.Dialog.abortPrompt();
+ this.shutdownPrompt();
+ ]]>
+ </body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ switch (aEvent.type) {
+ case "resize":
+ this.onResize();
+ break;
+ case "unload":
+ case "TabClose":
+ this.abortPrompt();
+ break;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="onResize">
+ <body>
+ <![CDATA[
+ let availWidth = this.clientWidth;
+ let availHeight = this.clientHeight;
+ if (availWidth == this.availWidth && availHeight == this.availHeight)
+ return;
+ this.availWidth = availWidth;
+ this.availHeight = availHeight;
+
+ let self = this;
+ function getElement(anonid) {
+ return document.getAnonymousElementByAttribute(self, "anonid", anonid);
+ }
+ let main = getElement("mainContainer");
+ let info = getElement("infoContainer");
+ let body = this.ui.infoBody;
+
+ // cap prompt dimensions at 60% width and 60% height of content area
+ if (!this.minWidth)
+ this.minWidth = parseInt(window.getComputedStyle(main).minWidth);
+ if (!this.minHeight)
+ this.minHeight = parseInt(window.getComputedStyle(main).minHeight);
+ let maxWidth = Math.max(Math.floor(availWidth * 0.6), this.minWidth) +
+ info.clientWidth - main.clientWidth;
+ let maxHeight = Math.max(Math.floor(availHeight * 0.6), this.minHeight) +
+ info.clientHeight - main.clientHeight;
+ body.style.maxWidth = maxWidth + "px";
+ info.style.overflow = info.style.width = info.style.height = "";
+
+ // when prompt text is too long, use scrollbars
+ if (info.clientWidth > maxWidth) {
+ info.style.overflow = "auto";
+ info.style.width = maxWidth + "px";
+ }
+ if (info.clientHeight > maxHeight) {
+ info.style.overflow = "auto";
+ info.style.height = maxHeight + "px";
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="onButtonClick">
+ <parameter name="buttonNum"/>
+ <body>
+ <![CDATA[
+ // We want to do all the work her asynchronously off a Gecko
+ // runnable, because of situations like the one described in
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1167575#c35 : we
+ // get here off processing of an OS event and will also process
+ // one more Gecko runnable before we break out of the event loop
+ // spin whoever posted the prompt is doing. If we do all our
+ // work sync, we will exit modal state _before_ processing that
+ // runnable, and if exiting moral state posts a runnable we will
+ // incorrectly process that runnable before leaving our event
+ // loop spin.
+ Services.tm.mainThread.dispatch(() => {
+ this.Dialog["onButton" + buttonNum]();
+ this.shutdownPrompt();
+ },
+ Ci.nsIThread.DISPATCH_NORMAL);
+ ]]>
+ </body>
+ </method>
+
+ <method name="onKeyAction">
+ <parameter name="action"/>
+ <parameter name="event"/>
+ <body>
+ <![CDATA[
+ if (event.defaultPrevented)
+ return;
+
+ event.stopPropagation();
+ if (action == "default") {
+ let bnum = this.args.defaultButtonNum || 0;
+ this.onButtonClick(bnum);
+ } else { // action == "cancel"
+ this.onButtonClick(1); // Cancel button
+ }
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <!-- Based on dialog.xml handlers -->
+ <handler event="keypress" keycode="VK_RETURN"
+ group="system" action="this.onKeyAction('default', event);"/>
+ <handler event="keypress" keycode="VK_ESCAPE"
+ group="system" action="this.onKeyAction('cancel', event);"/>
+#ifdef XP_MACOSX
+ <handler event="keypress" key="." modifiers="meta"
+ group="system" action="this.onKeyAction('cancel', event);"/>
+#endif
+ <handler event="focus" phase="capturing">
+ let bnum = this.args.defaultButtonNum || 0;
+ let defaultButton = this.ui["button" + bnum];
+
+ let { AppConstants } =
+ Components.utils.import("resource://gre/modules/AppConstants.jsm", {});
+ if (AppConstants.platform == "macosx") {
+ // On OS X, the default button always stays marked as such (until
+ // the entire prompt blurs).
+ defaultButton.setAttribute("default", true);
+ } else {
+ // On other platforms, the default button is only marked as such
+ // when no other button has focus. XUL buttons on not-OSX will
+ // react to pressing enter as a command, so you can't trigger the
+ // default without tabbing to it or something that isn't a button.
+ let focusedDefault = (event.originalTarget == defaultButton);
+ let someButtonFocused = event.originalTarget instanceof Ci.nsIDOMXULButtonElement;
+ defaultButton.setAttribute("default", focusedDefault || !someButtonFocused);
+ }
+ </handler>
+ <handler event="blur">
+ // If focus shifted to somewhere else in the browser, don't make
+ // the default button look active.
+ let bnum = this.args.defaultButtonNum || 0;
+ let button = this.ui["button" + bnum];
+ button.setAttribute("default", false);
+ </handler>
+ </handlers>
+
+ </binding>
+</bindings>
diff --git a/toolkit/components/prompts/jar.mn b/toolkit/components/prompts/jar.mn
new file mode 100644
index 000000000..60ecbdcbc
--- /dev/null
+++ b/toolkit/components/prompts/jar.mn
@@ -0,0 +1,12 @@
+# 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/.
+
+toolkit.jar:
+ content/global/commonDialog.js (content/commonDialog.js)
+* content/global/commonDialog.xul (content/commonDialog.xul)
+ content/global/commonDialog.css (content/commonDialog.css)
+ content/global/selectDialog.js (content/selectDialog.js)
+ content/global/selectDialog.xul (content/selectDialog.xul)
+ content/global/tabprompts.css (content/tabprompts.css)
+* content/global/tabprompts.xml (content/tabprompts.xml)
diff --git a/toolkit/components/prompts/moz.build b/toolkit/components/prompts/moz.build
new file mode 100644
index 000000000..1dc21cca6
--- /dev/null
+++ b/toolkit/components/prompts/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DIRS += ['src']
+
+MOCHITEST_MANIFESTS += ['test/mochitest.ini']
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/toolkit/components/prompts/src/CommonDialog.jsm b/toolkit/components/prompts/src/CommonDialog.jsm
new file mode 100644
index 000000000..c4200feb3
--- /dev/null
+++ b/toolkit/components/prompts/src/CommonDialog.jsm
@@ -0,0 +1,308 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = ["CommonDialog"];
+
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "EnableDelayHelper",
+ "resource://gre/modules/SharedPromptUtils.jsm");
+
+
+this.CommonDialog = function CommonDialog(args, ui) {
+ this.args = args;
+ this.ui = ui;
+}
+
+CommonDialog.prototype = {
+ args : null,
+ ui : null,
+
+ hasInputField : true,
+ numButtons : undefined,
+ iconClass : undefined,
+ soundID : undefined,
+ focusTimer : null,
+
+ onLoad : function(xulDialog) {
+ switch (this.args.promptType) {
+ case "alert":
+ case "alertCheck":
+ this.hasInputField = false;
+ this.numButtons = 1;
+ this.iconClass = ["alert-icon"];
+ this.soundID = Ci.nsISound.EVENT_ALERT_DIALOG_OPEN;
+ break;
+ case "confirmCheck":
+ case "confirm":
+ this.hasInputField = false;
+ this.numButtons = 2;
+ this.iconClass = ["question-icon"];
+ this.soundID = Ci.nsISound.EVENT_CONFIRM_DIALOG_OPEN;
+ break;
+ case "confirmEx":
+ var numButtons = 0;
+ if (this.args.button0Label)
+ numButtons++;
+ if (this.args.button1Label)
+ numButtons++;
+ if (this.args.button2Label)
+ numButtons++;
+ if (this.args.button3Label)
+ numButtons++;
+ if (numButtons == 0)
+ throw "A dialog with no buttons? Can not haz.";
+ this.numButtons = numButtons;
+ this.hasInputField = false;
+ this.iconClass = ["question-icon"];
+ this.soundID = Ci.nsISound.EVENT_CONFIRM_DIALOG_OPEN;
+ break;
+ case "prompt":
+ this.numButtons = 2;
+ this.iconClass = ["question-icon"];
+ this.soundID = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
+ this.initTextbox("login", this.args.value);
+ // Clear the label, since this isn't really a username prompt.
+ this.ui.loginLabel.setAttribute("value", "");
+ break;
+ case "promptUserAndPass":
+ this.numButtons = 2;
+ this.iconClass = ["authentication-icon", "question-icon"];
+ this.soundID = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
+ this.initTextbox("login", this.args.user);
+ this.initTextbox("password1", this.args.pass);
+ break;
+ case "promptPassword":
+ this.numButtons = 2;
+ this.iconClass = ["authentication-icon", "question-icon"];
+ this.soundID = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
+ this.initTextbox("password1", this.args.pass);
+ // Clear the label, since the message presumably indicates its purpose.
+ this.ui.password1Label.setAttribute("value", "");
+ break;
+ default:
+ Cu.reportError("commonDialog opened for unknown type: " + this.args.promptType);
+ throw "unknown dialog type";
+ }
+
+ // set the document title
+ let title = this.args.title;
+ // OS X doesn't have a title on modal dialogs, this is hidden on other platforms.
+ let infoTitle = this.ui.infoTitle;
+ infoTitle.appendChild(infoTitle.ownerDocument.createTextNode(title));
+ if (xulDialog)
+ xulDialog.ownerDocument.title = title;
+
+ // Set button labels and visibility
+ //
+ // This assumes that button0 defaults to a visible "ok" button, and
+ // button1 defaults to a visible "cancel" button. The other 2 buttons
+ // have no default labels (and are hidden).
+ switch (this.numButtons) {
+ case 4:
+ this.setLabelForNode(this.ui.button3, this.args.button3Label);
+ this.ui.button3.hidden = false;
+ // fall through
+ case 3:
+ this.setLabelForNode(this.ui.button2, this.args.button2Label);
+ this.ui.button2.hidden = false;
+ // fall through
+ case 2:
+ // Defaults to a visible "cancel" button
+ if (this.args.button1Label)
+ this.setLabelForNode(this.ui.button1, this.args.button1Label);
+ break;
+
+ case 1:
+ this.ui.button1.hidden = true;
+ break;
+ }
+ // Defaults to a visible "ok" button
+ if (this.args.button0Label)
+ this.setLabelForNode(this.ui.button0, this.args.button0Label);
+
+ // display the main text
+ let croppedMessage = "";
+ if (this.args.text) {
+ // Bug 317334 - crop string length as a workaround.
+ croppedMessage = this.args.text.substr(0, 10000);
+ }
+ let infoBody = this.ui.infoBody;
+ infoBody.appendChild(infoBody.ownerDocument.createTextNode(croppedMessage));
+
+ let label = this.args.checkLabel;
+ if (label) {
+ // Only show the checkbox if label has a value.
+ this.ui.checkboxContainer.hidden = false;
+ this.setLabelForNode(this.ui.checkbox, label);
+ this.ui.checkbox.checked = this.args.checked;
+ }
+
+ // set the icon
+ let icon = this.ui.infoIcon;
+ if (icon)
+ this.iconClass.forEach((el, idx, arr) => icon.classList.add(el));
+
+ // set default result to cancelled
+ this.args.ok = false;
+ this.args.buttonNumClicked = 1;
+
+
+ // Set the default button
+ let b = (this.args.defaultButtonNum || 0);
+ let button = this.ui["button" + b];
+
+ if (xulDialog)
+ xulDialog.defaultButton = ['accept', 'cancel', 'extra1', 'extra2'][b];
+ else
+ button.setAttribute("default", "true");
+
+ // Set default focus / selection.
+ this.setDefaultFocus(true);
+
+ if (this.args.enableDelay) {
+ this.delayHelper = new EnableDelayHelper({
+ disableDialog: () => this.setButtonsEnabledState(false),
+ enableDialog: () => this.setButtonsEnabledState(true),
+ focusTarget: this.ui.focusTarget
+ });
+ }
+
+ // Play a sound (unless we're tab-modal -- don't want those to feel like OS prompts).
+ try {
+ if (xulDialog && this.soundID) {
+ Cc["@mozilla.org/sound;1"].
+ createInstance(Ci.nsISound).
+ playEventSound(this.soundID);
+ }
+ } catch (e) {
+ Cu.reportError("Couldn't play common dialog event sound: " + e);
+ }
+
+ let topic = "common-dialog-loaded";
+ if (!xulDialog)
+ topic = "tabmodal-dialog-loaded";
+ Services.obs.notifyObservers(this.ui.prompt, topic, null);
+ },
+
+ setLabelForNode: function(aNode, aLabel) {
+ // This is for labels which may contain embedded access keys.
+ // If we end in (&X) where X represents the access key, optionally preceded
+ // by spaces and/or followed by the ':' character, store the access key and
+ // remove the access key placeholder + leading spaces from the label.
+ // Otherwise a character preceded by one but not two &s is the access key.
+ // Store it and remove the &.
+
+ // Note that if you change the following code, see the comment of
+ // nsTextBoxFrame::UpdateAccessTitle.
+ var accessKey = null;
+ if (/ *\(\&([^&])\)(:?)$/.test(aLabel)) {
+ aLabel = RegExp.leftContext + RegExp.$2;
+ accessKey = RegExp.$1;
+ } else if (/^([^&]*)\&(([^&]).*$)/.test(aLabel)) {
+ aLabel = RegExp.$1 + RegExp.$2;
+ accessKey = RegExp.$3;
+ }
+
+ // && is the magic sequence to embed an & in your label.
+ aLabel = aLabel.replace(/\&\&/g, "&");
+ aNode.label = aLabel;
+
+ // XXXjag bug 325251
+ // Need to set this after aNode.setAttribute("value", aLabel);
+ if (accessKey)
+ aNode.accessKey = accessKey;
+ },
+
+
+ initTextbox : function (aName, aValue) {
+ this.ui[aName + "Container"].hidden = false;
+ this.ui[aName + "Textbox"].setAttribute("value",
+ aValue !== null ? aValue : "");
+ },
+
+ setButtonsEnabledState : function(enabled) {
+ this.ui.button0.disabled = !enabled;
+ // button1 (cancel) remains enabled.
+ this.ui.button2.disabled = !enabled;
+ this.ui.button3.disabled = !enabled;
+ },
+
+ setDefaultFocus : function(isInitialLoad) {
+ let b = (this.args.defaultButtonNum || 0);
+ let button = this.ui["button" + b];
+
+ if (!this.hasInputField) {
+ let isOSX = ("nsILocalFileMac" in Components.interfaces);
+ if (isOSX)
+ this.ui.infoBody.focus();
+ else
+ button.focus();
+ } else if (this.args.promptType == "promptPassword") {
+ // When the prompt is initialized, focus and select the textbox
+ // contents. Afterwards, only focus the textbox.
+ if (isInitialLoad)
+ this.ui.password1Textbox.select();
+ else
+ this.ui.password1Textbox.focus();
+ } else if (isInitialLoad) {
+ this.ui.loginTextbox.select();
+ } else {
+ this.ui.loginTextbox.focus();
+ }
+ },
+
+ onCheckbox : function() {
+ this.args.checked = this.ui.checkbox.checked;
+ },
+
+ onButton0 : function() {
+ this.args.promptActive = false;
+ this.args.ok = true;
+ this.args.buttonNumClicked = 0;
+
+ let username = this.ui.loginTextbox.value;
+ let password = this.ui.password1Textbox.value;
+
+ // Return textfield values
+ switch (this.args.promptType) {
+ case "prompt":
+ this.args.value = username;
+ break;
+ case "promptUserAndPass":
+ this.args.user = username;
+ this.args.pass = password;
+ break;
+ case "promptPassword":
+ this.args.pass = password;
+ break;
+ }
+ },
+
+ onButton1 : function() {
+ this.args.promptActive = false;
+ this.args.buttonNumClicked = 1;
+ },
+
+ onButton2 : function() {
+ this.args.promptActive = false;
+ this.args.buttonNumClicked = 2;
+ },
+
+ onButton3 : function() {
+ this.args.promptActive = false;
+ this.args.buttonNumClicked = 3;
+ },
+
+ abortPrompt : function() {
+ this.args.promptActive = false;
+ this.args.promptAborted = true;
+ },
+
+};
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) { }
+ };
+}
diff --git a/toolkit/components/prompts/src/moz.build b/toolkit/components/prompts/src/moz.build
new file mode 100644
index 000000000..b13e47b42
--- /dev/null
+++ b/toolkit/components/prompts/src/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXTRA_COMPONENTS += [
+ 'nsPrompter.js',
+ 'nsPrompter.manifest',
+]
+
+EXTRA_JS_MODULES += [
+ 'CommonDialog.jsm',
+ 'SharedPromptUtils.jsm',
+]
+
diff --git a/toolkit/components/prompts/src/nsPrompter.js b/toolkit/components/prompts/src/nsPrompter.js
new file mode 100644
index 000000000..26efe28cc
--- /dev/null
+++ b/toolkit/components/prompts/src/nsPrompter.js
@@ -0,0 +1,958 @@
+/* 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/. */
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/SharedPromptUtils.jsm");
+
+function Prompter() {
+ // Note that EmbedPrompter clones this implementation.
+}
+
+Prompter.prototype = {
+ classID : Components.ID("{1c978d25-b37f-43a8-a2d6-0c7a239ead87}"),
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIPromptFactory, Ci.nsIPromptService, Ci.nsIPromptService2]),
+
+
+ /* ---------- private members ---------- */
+
+ pickPrompter : function (domWin) {
+ return new ModalPrompter(domWin);
+ },
+
+
+ /* ---------- nsIPromptFactory ---------- */
+
+
+ getPrompt : function (domWin, iid) {
+ // This is still kind of dumb; the C++ code delegated to login manager
+ // here, which in turn calls back into us via nsIPromptService2.
+ if (iid.equals(Ci.nsIAuthPrompt2) || iid.equals(Ci.nsIAuthPrompt)) {
+ try {
+ let pwmgr = Cc["@mozilla.org/passwordmanager/authpromptfactory;1"].
+ getService(Ci.nsIPromptFactory);
+ return pwmgr.getPrompt(domWin, iid);
+ } catch (e) {
+ Cu.reportError("nsPrompter: Delegation to password manager failed: " + e);
+ }
+ }
+
+ let p = new ModalPrompter(domWin);
+ p.QueryInterface(iid);
+ return p;
+ },
+
+
+ /* ---------- nsIPromptService ---------- */
+
+
+ alert : function (domWin, title, text) {
+ let p = this.pickPrompter(domWin);
+ p.alert(title, text);
+ },
+
+ alertCheck : function (domWin, title, text, checkLabel, checkValue) {
+ let p = this.pickPrompter(domWin);
+ p.alertCheck(title, text, checkLabel, checkValue);
+ },
+
+ confirm : function (domWin, title, text) {
+ let p = this.pickPrompter(domWin);
+ return p.confirm(title, text);
+ },
+
+ confirmCheck : function (domWin, title, text, checkLabel, checkValue) {
+ let p = this.pickPrompter(domWin);
+ return p.confirmCheck(title, text, checkLabel, checkValue);
+ },
+
+ confirmEx : function (domWin, title, text, flags, button0, button1, button2, checkLabel, checkValue) {
+ let p = this.pickPrompter(domWin);
+ return p.confirmEx(title, text, flags, button0, button1, button2, checkLabel, checkValue);
+ },
+
+ prompt : function (domWin, title, text, value, checkLabel, checkValue) {
+ let p = this.pickPrompter(domWin);
+ return p.nsIPrompt_prompt(title, text, value, checkLabel, checkValue);
+ },
+
+ promptUsernameAndPassword : function (domWin, title, text, user, pass, checkLabel, checkValue) {
+ let p = this.pickPrompter(domWin);
+ return p.nsIPrompt_promptUsernameAndPassword(title, text, user, pass, checkLabel, checkValue);
+ },
+
+ promptPassword : function (domWin, title, text, pass, checkLabel, checkValue) {
+ let p = this.pickPrompter(domWin);
+ return p.nsIPrompt_promptPassword(title, text, pass, checkLabel, checkValue);
+ },
+
+ select : function (domWin, title, text, count, list, selected) {
+ let p = this.pickPrompter(domWin);
+ return p.select(title, text, count, list, selected);
+ },
+
+
+ /* ---------- nsIPromptService2 ---------- */
+
+
+ promptAuth : function (domWin, channel, level, authInfo, checkLabel, checkValue) {
+ let p = this.pickPrompter(domWin);
+ return p.promptAuth(channel, level, authInfo, checkLabel, checkValue);
+ },
+
+ asyncPromptAuth : function (domWin, channel, callback, context, level, authInfo, checkLabel, checkValue) {
+ let p = this.pickPrompter(domWin);
+ return p.asyncPromptAuth(channel, callback, context, level, authInfo, checkLabel, checkValue);
+ },
+
+};
+
+
+// Common utils not specific to a particular prompter style.
+var PromptUtilsTemp = {
+ __proto__ : PromptUtils,
+
+ getLocalizedString : function (key, formatArgs) {
+ if (formatArgs)
+ return this.strBundle.formatStringFromName(key, formatArgs, formatArgs.length);
+ return this.strBundle.GetStringFromName(key);
+ },
+
+ confirmExHelper : function (flags, button0, button1, button2) {
+ const BUTTON_DEFAULT_MASK = 0x03000000;
+ let defaultButtonNum = (flags & BUTTON_DEFAULT_MASK) >> 24;
+ let isDelayEnabled = (flags & Ci.nsIPrompt.BUTTON_DELAY_ENABLE);
+
+ // Flags can be used to select a specific pre-defined button label or
+ // a caller-supplied string (button0/button1/button2). If no flags are
+ // set for a button, then the button won't be shown.
+ let argText = [button0, button1, button2];
+ let buttonLabels = [null, null, null];
+ for (let i = 0; i < 3; i++) {
+ let buttonLabel;
+ switch (flags & 0xff) {
+ case Ci.nsIPrompt.BUTTON_TITLE_OK:
+ buttonLabel = PromptUtils.getLocalizedString("OK");
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_CANCEL:
+ buttonLabel = PromptUtils.getLocalizedString("Cancel");
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_YES:
+ buttonLabel = PromptUtils.getLocalizedString("Yes");
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_NO:
+ buttonLabel = PromptUtils.getLocalizedString("No");
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_SAVE:
+ buttonLabel = PromptUtils.getLocalizedString("Save");
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_DONT_SAVE:
+ buttonLabel = PromptUtils.getLocalizedString("DontSave");
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_REVERT:
+ buttonLabel = PromptUtils.getLocalizedString("Revert");
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_IS_STRING:
+ buttonLabel = argText[i];
+ break;
+ }
+ if (buttonLabel)
+ buttonLabels[i] = buttonLabel;
+ flags >>= 8;
+ }
+
+ return [buttonLabels[0], buttonLabels[1], buttonLabels[2], defaultButtonNum, isDelayEnabled];
+ },
+
+ getAuthInfo : function (authInfo) {
+ let username, password;
+
+ let flags = authInfo.flags;
+ if (flags & Ci.nsIAuthInformation.NEED_DOMAIN && authInfo.domain)
+ username = authInfo.domain + "\\" + authInfo.username;
+ else
+ username = authInfo.username;
+
+ password = authInfo.password;
+
+ return [username, password];
+ },
+
+ setAuthInfo : function (authInfo, username, password) {
+ let flags = authInfo.flags;
+ if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
+ // Domain is separated from username by a backslash
+ let idx = username.indexOf("\\");
+ if (idx == -1) {
+ authInfo.username = username;
+ } else {
+ authInfo.domain = username.substring(0, idx);
+ authInfo.username = username.substring(idx+1);
+ }
+ } else {
+ authInfo.username = username;
+ }
+ authInfo.password = password;
+ },
+
+ /**
+ * Strip out things like userPass and path for display.
+ */
+ getFormattedHostname : function (uri) {
+ return uri.scheme + "://" + uri.hostPort;
+ },
+
+ // Copied from login manager
+ getAuthTarget : function (aChannel, aAuthInfo) {
+ let hostname, realm;
+
+ // If our proxy is demanding authentication, don't use the
+ // channel's actual destination.
+ if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
+ if (!(aChannel instanceof Ci.nsIProxiedChannel))
+ throw "proxy auth needs nsIProxiedChannel";
+
+ let info = aChannel.proxyInfo;
+ if (!info)
+ throw "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.
+ let idnService = Cc["@mozilla.org/network/idn-service;1"].
+ getService(Ci.nsIIDNService);
+ hostname = "moz-proxy://" +
+ idnService.convertUTF8toACE(info.host) +
+ ":" + info.port;
+ realm = aAuthInfo.realm;
+ if (!realm)
+ realm = hostname;
+
+ return [hostname, realm];
+ }
+
+ hostname = this.getFormattedHostname(aChannel.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 = aAuthInfo.realm;
+ if (!realm)
+ realm = hostname;
+
+ return [hostname, realm];
+ },
+
+
+ makeAuthMessage : function (channel, authInfo) {
+ let isProxy = (authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY);
+ let isPassOnly = (authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD);
+ let isCrossOrig = (authInfo.flags &
+ Ci.nsIAuthInformation.CROSS_ORIGIN_SUB_RESOURCE);
+
+ let username = authInfo.username;
+ let [displayHost, realm] = this.getAuthTarget(channel, authInfo);
+
+ // Suppress "the site says: $realm" when we synthesized a missing realm.
+ if (!authInfo.realm && !isProxy)
+ realm = "";
+
+ // Trim obnoxiously long realms.
+ if (realm.length > 150) {
+ realm = realm.substring(0, 150);
+ // Append "..." (or localized equivalent).
+ realm += this.ellipsis;
+ }
+
+ let text;
+ if (isProxy) {
+ text = PromptUtils.getLocalizedString("EnterLoginForProxy3", [realm, displayHost]);
+ } else if (isPassOnly) {
+ text = PromptUtils.getLocalizedString("EnterPasswordFor", [username, displayHost]);
+ } else if (isCrossOrig) {
+ text = PromptUtils.getLocalizedString("EnterUserPasswordForCrossOrigin2", [displayHost]);
+ } else if (!realm) {
+ text = PromptUtils.getLocalizedString("EnterUserPasswordFor2", [displayHost]);
+ } else {
+ text = PromptUtils.getLocalizedString("EnterLoginForRealm3", [realm, displayHost]);
+ }
+
+ return text;
+ },
+
+ getTabModalPrompt : function (domWin) {
+ var promptBox = null;
+
+ try {
+ // Get the topmost window, in case we're in a frame.
+ var promptWin = domWin.top;
+
+ // Get the chrome window for the content window we're using.
+ // (Unwrap because we need a non-IDL property below.)
+ var chromeWin = promptWin.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler.ownerDocument
+ .defaultView.wrappedJSObject;
+
+ if (chromeWin.getTabModalPromptBox)
+ promptBox = chromeWin.getTabModalPromptBox(promptWin);
+ } catch (e) {
+ // If any errors happen, just assume no tabmodal prompter.
+ }
+
+ return promptBox;
+ },
+};
+
+PromptUtils = PromptUtilsTemp;
+
+XPCOMUtils.defineLazyGetter(PromptUtils, "strBundle", function () {
+ let bunService = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService);
+ let bundle = bunService.createBundle("chrome://global/locale/commonDialogs.properties");
+ if (!bundle)
+ throw "String bundle for Prompter not present!";
+ return bundle;
+});
+
+XPCOMUtils.defineLazyGetter(PromptUtils, "ellipsis", function () {
+ let ellipsis = "\u2026";
+ try {
+ ellipsis = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data;
+ } catch (e) { }
+ return ellipsis;
+});
+
+
+
+function openModalWindow(domWin, uri, args) {
+ // There's an implied contract that says modal prompts should still work
+ // when no "parent" window is passed for the dialog (eg, the "Master
+ // Password" dialog does this). These prompts must be shown even if there
+ // are *no* visible windows at all.
+ // There's also a requirement for prompts to be blocked if a window is
+ // passed and that window is hidden (eg, auth prompts are supressed if the
+ // passed window is the hidden window).
+ // See bug 875157 comment 30 for more...
+ if (domWin) {
+ // a domWin was passed, so we can apply the check for it being hidden.
+ let winUtils = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ if (winUtils && !winUtils.isParentWindowMainWidgetVisible) {
+ throw Components.Exception("Cannot call openModalWindow on a hidden window",
+ Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+ } else {
+ // We try and find a window to use as the parent, but don't consider
+ // if that is visible before showing the prompt.
+ domWin = Services.ww.activeWindow;
+ // domWin may still be null here if there are _no_ windows open.
+ }
+ // Note that we don't need to fire DOMWillOpenModalDialog and
+ // DOMModalDialogClosed events here, wwatcher's OpenWindowInternal
+ // will do that. Similarly for enterModalState / leaveModalState.
+
+ Services.ww.openWindow(domWin, uri, "_blank", "centerscreen,chrome,modal,titlebar", args);
+}
+
+function openTabPrompt(domWin, tabPrompt, args) {
+ let docShell = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell);
+ let inPermitUnload = docShell.contentViewer && docShell.contentViewer.inPermitUnload;
+ let eventDetail = Cu.cloneInto({tabPrompt: true, inPermitUnload}, domWin);
+ PromptUtils.fireDialogEvent(domWin, "DOMWillOpenModalDialog", null, eventDetail);
+
+ let winUtils = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ winUtils.enterModalState();
+
+ let frameMM = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+ frameMM.QueryInterface(Ci.nsIDOMEventTarget);
+
+ // We provide a callback so the prompt can close itself. We don't want to
+ // wait for this event loop to return... Otherwise the presence of other
+ // prompts on the call stack would in this dialog appearing unresponsive
+ // until the other prompts had been closed.
+ let callbackInvoked = false;
+ let newPrompt;
+ function onPromptClose(forceCleanup) {
+ if (!newPrompt && !forceCleanup)
+ return;
+ callbackInvoked = true;
+ if (newPrompt)
+ tabPrompt.removePrompt(newPrompt);
+
+ frameMM.removeEventListener("pagehide", pagehide, true);
+
+ winUtils.leaveModalState();
+
+ PromptUtils.fireDialogEvent(domWin, "DOMModalDialogClosed");
+ }
+
+ frameMM.addEventListener("pagehide", pagehide, true);
+ function pagehide(e) {
+ // Check whether the event relates to our window or its ancestors
+ let window = domWin;
+ let eventWindow = e.target.defaultView;
+ while (window != eventWindow && window.parent != window) {
+ window = window.parent;
+ }
+ if (window != eventWindow) {
+ return;
+ }
+ frameMM.removeEventListener("pagehide", pagehide, true);
+
+ if (newPrompt) {
+ newPrompt.abortPrompt();
+ }
+ }
+
+ try {
+ let topPrincipal = domWin.top.document.nodePrincipal;
+ let promptPrincipal = domWin.document.nodePrincipal;
+ args.showAlertOrigin = topPrincipal.equals(promptPrincipal);
+ args.promptActive = true;
+
+ newPrompt = tabPrompt.appendPrompt(args, onPromptClose);
+
+ // TODO since we don't actually open a window, need to check if
+ // there's other stuff in nsWindowWatcher::OpenWindowInternal
+ // that we might need to do here as well.
+
+ let thread = Services.tm.currentThread;
+ while (args.promptActive)
+ thread.processNextEvent(true);
+ delete args.promptActive;
+
+ if (args.promptAborted)
+ throw Components.Exception("prompt aborted by user", Cr.NS_ERROR_NOT_AVAILABLE);
+ } finally {
+ // If the prompt unexpectedly failed to invoke the callback, do so here.
+ if (!callbackInvoked)
+ onPromptClose(true);
+ }
+}
+
+function openRemotePrompt(domWin, args, tabPrompt) {
+ let docShell = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell);
+ let messageManager = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsITabChild)
+ .messageManager;
+
+ let inPermitUnload = docShell.contentViewer && docShell.contentViewer.inPermitUnload;
+ let eventDetail = Cu.cloneInto({tabPrompt, inPermitUnload}, domWin);
+ PromptUtils.fireDialogEvent(domWin, "DOMWillOpenModalDialog", null, eventDetail);
+
+ let winUtils = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ winUtils.enterModalState();
+ let closed = false;
+
+ let frameMM = docShell.getInterface(Ci.nsIContentFrameMessageManager);
+ frameMM.QueryInterface(Ci.nsIDOMEventTarget);
+
+ // It should be hard or impossible to cause a window to create multiple
+ // prompts, but just in case, give our prompt an ID.
+ let id = "id" + Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
+
+ messageManager.addMessageListener("Prompt:Close", function listener(message) {
+ if (message.data._remoteId !== id) {
+ return;
+ }
+
+ messageManager.removeMessageListener("Prompt:Close", listener);
+ frameMM.removeEventListener("pagehide", pagehide, true);
+
+ winUtils.leaveModalState();
+ PromptUtils.fireDialogEvent(domWin, "DOMModalDialogClosed");
+
+ // Copy the response from the closed prompt into our args, it will be
+ // read by our caller.
+ if (message.data) {
+ for (let key in message.data) {
+ args[key] = message.data[key];
+ }
+ }
+
+ // Exit our nested event loop when we unwind.
+ closed = true;
+ });
+
+ frameMM.addEventListener("pagehide", pagehide, true);
+ function pagehide(e) {
+ // Check whether the event relates to our window or its ancestors
+ let window = domWin;
+ let eventWindow = e.target.defaultView;
+ while (window != eventWindow && window.parent != window) {
+ window = window.parent;
+ }
+ if (window != eventWindow) {
+ return;
+ }
+ frameMM.removeEventListener("pagehide", pagehide, true);
+ messageManager.sendAsyncMessage("Prompt:ForceClose", { _remoteId: id });
+ }
+
+ let topPrincipal = domWin.top.document.nodePrincipal;
+ let promptPrincipal = domWin.document.nodePrincipal;
+ args.promptPrincipal = promptPrincipal;
+ args.showAlertOrigin = topPrincipal.equals(promptPrincipal);
+ args.inPermitUnload = inPermitUnload;
+
+ args._remoteId = id;
+
+ messageManager.sendAsyncMessage("Prompt:Open", args, {});
+
+ let thread = Services.tm.currentThread;
+ while (!closed) {
+ thread.processNextEvent(true);
+ }
+}
+
+function ModalPrompter(domWin) {
+ this.domWin = domWin;
+}
+ModalPrompter.prototype = {
+ domWin : null,
+ /*
+ * Default to not using a tab-modal prompt, unless the caller opts in by
+ * QIing to nsIWritablePropertyBag and setting the value of this property
+ * to true.
+ */
+ allowTabModal : false,
+
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIPrompt, Ci.nsIAuthPrompt,
+ Ci.nsIAuthPrompt2,
+ Ci.nsIWritablePropertyBag2]),
+
+
+ /* ---------- internal methods ---------- */
+
+
+ openPrompt : function (args) {
+ // Check pref, if false/missing do not ever allow tab-modal prompts.
+ const prefName = "prompts.tab_modal.enabled";
+ let prefValue = false;
+ if (Services.prefs.getPrefType(prefName) == Services.prefs.PREF_BOOL)
+ prefValue = Services.prefs.getBoolPref(prefName);
+
+ let allowTabModal = this.allowTabModal && prefValue;
+
+ if (allowTabModal && this.domWin) {
+ if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
+ openRemotePrompt(this.domWin, args, true);
+ return;
+ }
+
+ let tabPrompt = PromptUtils.getTabModalPrompt(this.domWin);
+ if (tabPrompt) {
+ openTabPrompt(this.domWin, tabPrompt, args);
+ return;
+ }
+ }
+
+ // If we can't do a tab modal prompt, fallback to using a window-modal dialog.
+ const COMMON_DIALOG = "chrome://global/content/commonDialog.xul";
+ const SELECT_DIALOG = "chrome://global/content/selectDialog.xul";
+
+ let uri = (args.promptType == "select") ? SELECT_DIALOG : COMMON_DIALOG;
+
+ if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
+ args.uri = uri;
+ openRemotePrompt(this.domWin, args);
+ return;
+ }
+
+ let propBag = PromptUtils.objectToPropBag(args);
+ openModalWindow(this.domWin, uri, propBag);
+ PromptUtils.propBagToObject(propBag, args);
+ },
+
+
+
+ /*
+ * ---------- interface disambiguation ----------
+ *
+ * nsIPrompt and nsIAuthPrompt share 3 method names with slightly
+ * different arguments. All but prompt() have the same number of
+ * arguments, so look at the arg types to figure out how we're being
+ * called. :-(
+ */
+ prompt : function() {
+ // also, the nsIPrompt flavor has 5 args instead of 6.
+ if (typeof arguments[2] == "object")
+ return this.nsIPrompt_prompt.apply(this, arguments);
+ return this.nsIAuthPrompt_prompt.apply(this, arguments);
+ },
+
+ promptUsernameAndPassword : function() {
+ // Both have 6 args, so use types.
+ if (typeof arguments[2] == "object")
+ return this.nsIPrompt_promptUsernameAndPassword.apply(this, arguments);
+ return this.nsIAuthPrompt_promptUsernameAndPassword.apply(this, arguments);
+ },
+
+ promptPassword : function() {
+ // Both have 5 args, so use types.
+ if (typeof arguments[2] == "object")
+ return this.nsIPrompt_promptPassword.apply(this, arguments);
+ return this.nsIAuthPrompt_promptPassword.apply(this, arguments);
+ },
+
+
+ /* ---------- nsIPrompt ---------- */
+
+
+ alert : function (title, text) {
+ if (!title)
+ title = PromptUtils.getLocalizedString("Alert");
+
+ let args = {
+ promptType: "alert",
+ title: title,
+ text: text,
+ };
+
+ this.openPrompt(args);
+ },
+
+ alertCheck : function (title, text, checkLabel, checkValue) {
+ if (!title)
+ title = PromptUtils.getLocalizedString("Alert");
+
+ let args = {
+ promptType: "alertCheck",
+ title: title,
+ text: text,
+ checkLabel: checkLabel,
+ checked: checkValue.value,
+ };
+
+ this.openPrompt(args);
+
+ // Checkbox state always returned, even if cancel clicked.
+ checkValue.value = args.checked;
+ },
+
+ confirm : function (title, text) {
+ if (!title)
+ title = PromptUtils.getLocalizedString("Confirm");
+
+ let args = {
+ promptType: "confirm",
+ title: title,
+ text: text,
+ ok: false,
+ };
+
+ this.openPrompt(args);
+
+ // Did user click Ok or Cancel?
+ return args.ok;
+ },
+
+ confirmCheck : function (title, text, checkLabel, checkValue) {
+ if (!title)
+ title = PromptUtils.getLocalizedString("ConfirmCheck");
+
+ let args = {
+ promptType: "confirmCheck",
+ title: title,
+ text: text,
+ checkLabel: checkLabel,
+ checked: checkValue.value,
+ ok: false,
+ };
+
+ this.openPrompt(args);
+
+ // Checkbox state always returned, even if cancel clicked.
+ checkValue.value = args.checked;
+
+ // Did user click Ok or Cancel?
+ return args.ok;
+ },
+
+ confirmEx : function (title, text, flags, button0, button1, button2,
+ checkLabel, checkValue) {
+
+ if (!title)
+ title = PromptUtils.getLocalizedString("Confirm");
+
+ let args = {
+ promptType: "confirmEx",
+ title: title,
+ text: text,
+ checkLabel: checkLabel,
+ checked: checkValue.value,
+ ok: false,
+ buttonNumClicked: 1,
+ };
+
+ let [label0, label1, label2, defaultButtonNum, isDelayEnabled] =
+ PromptUtils.confirmExHelper(flags, button0, button1, button2);
+
+ args.defaultButtonNum = defaultButtonNum;
+ args.enableDelay = isDelayEnabled;
+
+ if (label0) {
+ args.button0Label = label0;
+ if (label1) {
+ args.button1Label = label1;
+ if (label2) {
+ args.button2Label = label2;
+ }
+ }
+ }
+
+ this.openPrompt(args);
+
+ // Checkbox state always returned, even if cancel clicked.
+ checkValue.value = args.checked;
+
+ // Get the number of the button the user clicked.
+ return args.buttonNumClicked;
+ },
+
+ nsIPrompt_prompt : function (title, text, value, checkLabel, checkValue) {
+ if (!title)
+ title = PromptUtils.getLocalizedString("Prompt");
+
+ let args = {
+ promptType: "prompt",
+ title: title,
+ text: text,
+ value: value.value,
+ checkLabel: checkLabel,
+ checked: checkValue.value,
+ ok: false,
+ };
+
+ this.openPrompt(args);
+
+ // Did user click Ok or Cancel?
+ let ok = args.ok;
+ if (ok) {
+ checkValue.value = args.checked;
+ value.value = args.value;
+ }
+
+ return ok;
+ },
+
+ nsIPrompt_promptUsernameAndPassword : function (title, text, user, pass, checkLabel, checkValue) {
+ if (!title)
+ title = PromptUtils.getLocalizedString("PromptUsernameAndPassword2");
+
+ let args = {
+ promptType: "promptUserAndPass",
+ title: title,
+ text: text,
+ user: user.value,
+ pass: pass.value,
+ checkLabel: checkLabel,
+ checked: checkValue.value,
+ ok: false,
+ };
+
+ this.openPrompt(args);
+
+ // Did user click Ok or Cancel?
+ let ok = args.ok;
+ if (ok) {
+ checkValue.value = args.checked;
+ user.value = args.user;
+ pass.value = args.pass;
+ }
+
+ return ok;
+ },
+
+ nsIPrompt_promptPassword : function (title, text, pass, checkLabel, checkValue) {
+ if (!title)
+ title = PromptUtils.getLocalizedString("PromptPassword2");
+
+ let args = {
+ promptType: "promptPassword",
+ title: title,
+ text: text,
+ pass: pass.value,
+ checkLabel: checkLabel,
+ checked: checkValue.value,
+ ok: false,
+ }
+
+ this.openPrompt(args);
+
+ // Did user click Ok or Cancel?
+ let ok = args.ok;
+ if (ok) {
+ checkValue.value = args.checked;
+ pass.value = args.pass;
+ }
+
+ return ok;
+ },
+
+ select : function (title, text, count, list, selected) {
+ if (!title)
+ title = PromptUtils.getLocalizedString("Select");
+
+ let args = {
+ promptType: "select",
+ title: title,
+ text: text,
+ list: list,
+ selected: -1,
+ ok: false,
+ };
+
+ this.openPrompt(args);
+
+ // Did user click Ok or Cancel?
+ let ok = args.ok;
+ if (ok)
+ selected.value = args.selected;
+
+ return ok;
+ },
+
+
+ /* ---------- nsIAuthPrompt ---------- */
+
+
+ nsIAuthPrompt_prompt : function (title, text, passwordRealm, savePassword, defaultText, result) {
+ // The passwordRealm and savePassword args were ignored by nsPrompt.cpp
+ if (defaultText)
+ result.value = defaultText;
+ return this.nsIPrompt_prompt(title, text, result, null, {});
+ },
+
+ nsIAuthPrompt_promptUsernameAndPassword : function (title, text, passwordRealm, savePassword, user, pass) {
+ // The passwordRealm and savePassword args were ignored by nsPrompt.cpp
+ return this.nsIPrompt_promptUsernameAndPassword(title, text, user, pass, null, {});
+ },
+
+ nsIAuthPrompt_promptPassword : function (title, text, passwordRealm, savePassword, pass) {
+ // The passwordRealm and savePassword args were ignored by nsPrompt.cpp
+ return this.nsIPrompt_promptPassword(title, text, pass, null, {});
+ },
+
+
+ /* ---------- nsIAuthPrompt2 ---------- */
+
+
+ promptAuth : function (channel, level, authInfo, checkLabel, checkValue) {
+ let message = PromptUtils.makeAuthMessage(channel, authInfo);
+
+ let [username, password] = PromptUtils.getAuthInfo(authInfo);
+
+ let userParam = { value: username };
+ let passParam = { value: password };
+
+ let ok;
+ if (authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD)
+ ok = this.nsIPrompt_promptPassword(null, message, passParam, checkLabel, checkValue);
+ else
+ ok = this.nsIPrompt_promptUsernameAndPassword(null, message, userParam, passParam, checkLabel, checkValue);
+
+ if (ok)
+ PromptUtils.setAuthInfo(authInfo, userParam.value, passParam.value);
+ return ok;
+ },
+
+ asyncPromptAuth : function (channel, callback, context, level, authInfo, checkLabel, checkValue) {
+ // Nothing calls this directly; netwerk ends up going through
+ // nsIPromptService::GetPrompt, which delegates to login manager.
+ // Login manger handles the async bits itself, and only calls out
+ // promptAuth, never asyncPromptAuth.
+ //
+ // Bug 565582 will change this.
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ /* ---------- nsIWritablePropertyBag2 ---------- */
+
+ // Only a partial implementation, for one specific use case...
+
+ setPropertyAsBool : function(name, value) {
+ if (name == "allowTabModal")
+ this.allowTabModal = value;
+ else
+ throw Cr.NS_ERROR_ILLEGAL_VALUE;
+ },
+};
+
+
+function AuthPromptAdapterFactory() {
+}
+AuthPromptAdapterFactory.prototype = {
+ classID : Components.ID("{6e134924-6c3a-4d86-81ac-69432dd971dc}"),
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIAuthPromptAdapterFactory]),
+
+ /* ---------- nsIAuthPromptAdapterFactory ---------- */
+
+ createAdapter : function (oldPrompter) {
+ return new AuthPromptAdapter(oldPrompter);
+ }
+};
+
+
+// Takes an nsIAuthPrompt implementation, wraps it with a nsIAuthPrompt2 shell.
+function AuthPromptAdapter(oldPrompter) {
+ this.oldPrompter = oldPrompter;
+}
+AuthPromptAdapter.prototype = {
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIAuthPrompt2]),
+ oldPrompter : null,
+
+ /* ---------- nsIAuthPrompt2 ---------- */
+
+ promptAuth : function (channel, level, authInfo, checkLabel, checkValue) {
+ let message = PromptUtils.makeAuthMessage(channel, authInfo);
+
+ let [username, password] = PromptUtils.getAuthInfo(authInfo);
+ let userParam = { value: username };
+ let passParam = { value: password };
+
+ let [host, realm] = PromptUtils.getAuthTarget(channel, authInfo);
+ let authTarget = host + " (" + realm + ")";
+
+ let ok;
+ if (authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD)
+ ok = this.oldPrompter.promptPassword(null, message, authTarget, Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, passParam);
+ else
+ ok = this.oldPrompter.promptUsernameAndPassword(null, message, authTarget, Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, userParam, passParam);
+
+ if (ok)
+ PromptUtils.setAuthInfo(authInfo, userParam.value, passParam.value);
+ return ok;
+ },
+
+ asyncPromptAuth : function (channel, callback, context, level, authInfo, checkLabel, checkValue) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ }
+};
+
+
+// Wrapper using the old embedding contractID, since it's already common in
+// the addon ecosystem.
+function EmbedPrompter() {
+}
+EmbedPrompter.prototype = new Prompter();
+EmbedPrompter.prototype.classID = Components.ID("{7ad1b327-6dfa-46ec-9234-f2a620ea7e00}");
+
+var component = [Prompter, EmbedPrompter, AuthPromptAdapterFactory];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);
diff --git a/toolkit/components/prompts/src/nsPrompter.manifest b/toolkit/components/prompts/src/nsPrompter.manifest
new file mode 100644
index 000000000..582f6bf59
--- /dev/null
+++ b/toolkit/components/prompts/src/nsPrompter.manifest
@@ -0,0 +1,6 @@
+component {1c978d25-b37f-43a8-a2d6-0c7a239ead87} nsPrompter.js
+contract @mozilla.org/prompter;1 {1c978d25-b37f-43a8-a2d6-0c7a239ead87}
+component {6e134924-6c3a-4d86-81ac-69432dd971dc} nsPrompter.js
+contract @mozilla.org/network/authprompt-adapter-factory;1 {6e134924-6c3a-4d86-81ac-69432dd971dc}
+component {7ad1b327-6dfa-46ec-9234-f2a620ea7e00} nsPrompter.js
+contract @mozilla.org/embedcomp/prompt-service;1 {7ad1b327-6dfa-46ec-9234-f2a620ea7e00}
diff --git a/toolkit/components/prompts/test/.eslintrc.js b/toolkit/components/prompts/test/.eslintrc.js
new file mode 100644
index 000000000..3c788d6d6
--- /dev/null
+++ b/toolkit/components/prompts/test/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/mochitest/mochitest.eslintrc.js"
+ ]
+};
diff --git a/toolkit/components/prompts/test/bug619644_inner.html b/toolkit/components/prompts/test/bug619644_inner.html
new file mode 100644
index 000000000..f929c5649
--- /dev/null
+++ b/toolkit/components/prompts/test/bug619644_inner.html
@@ -0,0 +1,7 @@
+<head></head><body><p>Original content</p>
+<script>
+ window.opener.postMessage("", "*");
+ confirm ("Message");
+ document.write ("Extra content");
+ window.opener.postMessage(document.documentElement.innerHTML, "*");
+</script></body>
diff --git a/toolkit/components/prompts/test/bug625187_iframe.html b/toolkit/components/prompts/test/bug625187_iframe.html
new file mode 100644
index 000000000..740d59a61
--- /dev/null
+++ b/toolkit/components/prompts/test/bug625187_iframe.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+ <title>Test for Bug 625187 - the iframe</title>
+<!--
+ - Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/
+ -
+ - Contributor(s):
+ - Mihai Sucan <mihai.sucan@gmail.com>
+ -->
+</head>
+<body>
+<p><button id="btn1" onclick="alert('hello world 2')">Button 2</button></p>
+<p><button id="btn2" onclick="window.parent.alert('hello world 3')">Button 3</button></p>
+</body>
+</html>
diff --git a/toolkit/components/prompts/test/chromeScript.js b/toolkit/components/prompts/test/chromeScript.js
new file mode 100644
index 000000000..7b2d37100
--- /dev/null
+++ b/toolkit/components/prompts/test/chromeScript.js
@@ -0,0 +1,241 @@
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/Timer.jsm");
+
+// Define these to make EventUtils happy.
+let window = this;
+let parent = {};
+
+let EventUtils = {};
+Services.scriptloader.loadSubScript(
+ "chrome://mochikit/content/tests/SimpleTest/EventUtils.js",
+ EventUtils
+);
+
+addMessageListener("handlePrompt", msg => {
+ handlePromptWhenItAppears(msg.action, msg.isTabModal, msg.isSelect);
+});
+
+function handlePromptWhenItAppears(action, isTabModal, isSelect) {
+ let interval = setInterval(() => {
+ if (handlePrompt(action, isTabModal, isSelect)) {
+ clearInterval(interval);
+ }
+ }, 100);
+}
+
+function handlePrompt(action, isTabModal, isSelect) {
+ let ui;
+
+ if (isTabModal) {
+ let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
+ let gBrowser = browserWin.gBrowser;
+ let promptManager = gBrowser.getTabModalPromptBox(gBrowser.selectedBrowser);
+ let prompts = promptManager.listPrompts();
+ if (!prompts.length) {
+ return false; // try again in a bit
+ }
+
+ ui = prompts[0].Dialog.ui;
+ } else {
+ let doc = getDialogDoc();
+ if (!doc) {
+ return false; // try again in a bit
+ }
+
+ if (isSelect)
+ ui = doc;
+ else
+ ui = doc.defaultView.Dialog.ui;
+
+ }
+
+ let promptState;
+ if (isSelect) {
+ promptState = getSelectState(ui);
+ dismissSelect(ui, action);
+ } else {
+ promptState = getPromptState(ui);
+ dismissPrompt(ui, action);
+ }
+ sendAsyncMessage("promptHandled", { promptState: promptState });
+ return true;
+}
+
+function getSelectState(ui) {
+ let listbox = ui.getElementById("list");
+
+ let state = {};
+ state.msg = ui.getElementById("info.txt").value;
+ state.selectedIndex = listbox.selectedIndex;
+ state.items = [];
+
+ for (let i = 0; i < listbox.itemCount; i++) {
+ let item = listbox.getItemAtIndex(i).label;
+ state.items.push(item);
+ }
+
+ return state;
+}
+
+function getPromptState(ui) {
+ let state = {};
+ state.msg = ui.infoBody.textContent;
+ state.titleHidden = ui.infoTitle.getAttribute("hidden") == "true";
+ state.textHidden = ui.loginContainer.hidden;
+ state.passHidden = ui.password1Container.hidden;
+ state.checkHidden = ui.checkboxContainer.hidden;
+ state.checkMsg = ui.checkbox.label;
+ state.checked = ui.checkbox.checked;
+ // tab-modal prompts don't have an infoIcon
+ state.iconClass = ui.infoIcon ? ui.infoIcon.className : null;
+ state.textValue = ui.loginTextbox.getAttribute("value");
+ state.passValue = ui.password1Textbox.getAttribute("value");
+
+ state.butt0Label = ui.button0.label;
+ state.butt1Label = ui.button1.label;
+ state.butt2Label = ui.button2.label;
+
+ state.butt0Disabled = ui.button0.disabled;
+ state.butt1Disabled = ui.button1.disabled;
+ state.butt2Disabled = ui.button2.disabled;
+
+ function isDefaultButton(b) {
+ return (b.hasAttribute("default") &&
+ b.getAttribute("default") == "true");
+ }
+ state.defButton0 = isDefaultButton(ui.button0);
+ state.defButton1 = isDefaultButton(ui.button1);
+ state.defButton2 = isDefaultButton(ui.button2);
+
+ let fm = Cc["@mozilla.org/focus-manager;1"].
+ getService(Ci.nsIFocusManager);
+ let e = fm.focusedElement;
+
+ if (e == null) {
+ state.focused = null;
+ } else if (ui.button0.isSameNode(e)) {
+ state.focused = "button0";
+ } else if (ui.button1.isSameNode(e)) {
+ state.focused = "button1";
+ } else if (ui.button2.isSameNode(e)) {
+ state.focused = "button2";
+ } else if (ui.loginTextbox.inputField.isSameNode(e)) {
+ state.focused = "textField";
+ } else if (ui.password1Textbox.inputField.isSameNode(e)) {
+ state.focused = "passField";
+ } else if (ui.infoBody.isSameNode(e)) {
+ state.focused = "infoBody";
+ } else {
+ state.focused = "ERROR: unexpected element focused: " + (e ? e.localName : "<null>");
+ }
+
+ return state;
+}
+
+function dismissSelect(ui, action) {
+ let dialog = ui.getElementsByTagName("dialog")[0];
+ let listbox = ui.getElementById("list");
+
+ if (action.selectItem) {
+ listbox.selectedIndex = 1;
+ }
+
+ if (action.buttonClick == "ok") {
+ dialog.acceptDialog();
+ } else if (action.buttonClick == "cancel") {
+ dialog.cancelDialog();
+ }
+}
+
+function dismissPrompt(ui, action) {
+ if (action.setCheckbox) {
+ // Annoyingly, the prompt code is driven by oncommand.
+ ui.checkbox.setChecked(true);
+ ui.checkbox.doCommand();
+ }
+
+ if ("textField" in action) {
+ ui.loginTextbox.setAttribute("value", action.textField);
+ }
+
+ if ("passField" in action) {
+ ui.password1Textbox.setAttribute("value", action.passField);
+ }
+
+ switch (action.buttonClick) {
+ case "ok":
+ case 0:
+ ui.button0.click();
+ break;
+ case "cancel":
+ case 1:
+ ui.button1.click();
+ break;
+ case 2:
+ ui.button2.click();
+ break;
+ case "ESC":
+ // XXX This is assuming tab-modal.
+ let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
+ EventUtils.synthesizeKey("KEY_Escape", { code: "Escape" }, browserWin);
+ break;
+ case "pollOK":
+ // Buttons are disabled at the moment, poll until they're reenabled.
+ // Can't use setInterval here, because the window's in a modal state
+ // and thus DOM events are suppressed.
+ let interval = setInterval(() => {
+ if (ui.button0.disabled)
+ return;
+ ui.button0.click();
+ clearInterval(interval);
+ }, 100);
+ break;
+
+ default:
+ throw "dismissPrompt action listed unknown button.";
+ }
+}
+
+function getDialogDoc() {
+ // Trudge through all the open windows, until we find the one
+ // that has either commonDialog.xul or selectDialog.xul loaded.
+ var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator);
+ // var enumerator = wm.getEnumerator("navigator:browser");
+ var enumerator = wm.getXULWindowEnumerator(null);
+
+ while (enumerator.hasMoreElements()) {
+ var win = enumerator.getNext();
+ var windowDocShell = win.QueryInterface(Ci.nsIXULWindow).docShell;
+
+ var containedDocShells = windowDocShell.getDocShellEnumerator(
+ Ci.nsIDocShellTreeItem.typeChrome,
+ Ci.nsIDocShell.ENUMERATE_FORWARDS);
+ while (containedDocShells.hasMoreElements()) {
+ // Get the corresponding document for this docshell
+ var childDocShell = containedDocShells.getNext();
+ // We don't want it if it's not done loading.
+ if (childDocShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE)
+ continue;
+ var childDoc = childDocShell.QueryInterface(Ci.nsIDocShell)
+ .contentViewer
+ .DOMDocument;
+
+ if (childDoc.location.href != "chrome://global/content/commonDialog.xul" &&
+ childDoc.location.href != "chrome://global/content/selectDialog.xul")
+ continue;
+
+ // We're expecting the dialog to be focused. If it's not yet, try later.
+ // (In particular, this is needed on Linux to reliably check focused elements.)
+ let fm = Cc["@mozilla.org/focus-manager;1"].
+ getService(Ci.nsIFocusManager);
+ if (fm.focusedWindow != childDoc.defaultView)
+ continue;
+
+ return childDoc;
+ }
+ }
+
+ return null;
+}
diff --git a/toolkit/components/prompts/test/mochitest.ini b/toolkit/components/prompts/test/mochitest.ini
new file mode 100644
index 000000000..7f87650d6
--- /dev/null
+++ b/toolkit/components/prompts/test/mochitest.ini
@@ -0,0 +1,19 @@
+[DEFAULT]
+support-files =
+ ../../passwordmgr/test/authenticate.sjs
+ bug619644_inner.html
+ bug625187_iframe.html
+ prompt_common.js
+ chromeScript.js
+
+[test_bug619644.html]
+[test_bug620145.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_subresources_prompts.html]
+skip-if = toolkit == 'android'
+[test_dom_prompts.html]
+skip-if = toolkit == 'android' #android: bug 1267092
+[test_modal_prompts.html]
+skip-if = toolkit == 'android' || (os == 'linux' && (debug || asan)) #android: TIMED_OUT (For Linux : 950636)
+[test_modal_select.html]
+skip-if = toolkit == 'android' #android: TIMED_OUT
diff --git a/toolkit/components/prompts/test/prompt_common.js b/toolkit/components/prompts/test/prompt_common.js
new file mode 100644
index 000000000..e3a69b347
--- /dev/null
+++ b/toolkit/components/prompts/test/prompt_common.js
@@ -0,0 +1,158 @@
+const Ci = SpecialPowers.Ci;
+const Cc = SpecialPowers.Cc;
+ok(Ci != null, "Access Ci");
+ok(Cc != null, "Access Cc");
+
+function hasTabModalPrompts() {
+ var prefName = "prompts.tab_modal.enabled";
+ var Services = SpecialPowers.Cu.import("resource://gre/modules/Services.jsm").Services;
+ return Services.prefs.getPrefType(prefName) == Services.prefs.PREF_BOOL &&
+ Services.prefs.getBoolPref(prefName);
+}
+var isTabModal = hasTabModalPrompts();
+var isSelectDialog = false;
+var isOSX = ("nsILocalFileMac" in SpecialPowers.Ci);
+var isE10S = SpecialPowers.Services.appinfo.processType == 2;
+
+
+var gChromeScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL("chromeScript.js"));
+SimpleTest.registerCleanupFunction(() => gChromeScript.destroy());
+
+function onloadPromiseFor(id) {
+ var iframe = document.getElementById(id);
+ return new Promise(resolve => {
+ iframe.addEventListener("load", function onload(e) {
+ iframe.removeEventListener("load", onload);
+ resolve(true);
+ });
+ });
+}
+
+function handlePrompt(state, action) {
+ return new Promise(resolve => {
+ gChromeScript.addMessageListener("promptHandled", function handled(msg) {
+ gChromeScript.removeMessageListener("promptHandled", handled);
+ checkPromptState(msg.promptState, state);
+ resolve(true);
+ });
+ gChromeScript.sendAsyncMessage("handlePrompt", { action: action, isTabModal: isTabModal});
+ });
+}
+
+function checkPromptState(promptState, expectedState) {
+ // XXX check title? OS X has title in content
+ is(promptState.msg, expectedState.msg, "Checking expected message");
+ if (isOSX && !isTabModal)
+ ok(!promptState.titleHidden, "Checking title always visible on OS X");
+ else
+ is(promptState.titleHidden, expectedState.titleHidden, "Checking title visibility");
+ is(promptState.textHidden, expectedState.textHidden, "Checking textbox visibility");
+ is(promptState.passHidden, expectedState.passHidden, "Checking passbox visibility");
+ is(promptState.checkHidden, expectedState.checkHidden, "Checking checkbox visibility");
+ is(promptState.checkMsg, expectedState.checkMsg, "Checking checkbox label");
+ is(promptState.checked, expectedState.checked, "Checking checkbox checked");
+ if (!isTabModal)
+ is(promptState.iconClass, "spaced " + expectedState.iconClass, "Checking expected icon CSS class");
+ is(promptState.textValue, expectedState.textValue, "Checking textbox value");
+ is(promptState.passValue, expectedState.passValue, "Checking passbox value");
+
+ if (expectedState.butt0Label) {
+ is(promptState.butt0Label, expectedState.butt0Label, "Checking accept-button label");
+ }
+ if (expectedState.butt1Label) {
+ is(promptState.butt1Label, expectedState.butt1Label, "Checking cancel-button label");
+ }
+ if (expectedState.butt2Label) {
+ is(promptState.butt2Label, expectedState.butt2Label, "Checking extra1-button label");
+ }
+
+ // For prompts with a time-delay button.
+ if (expectedState.butt0Disabled) {
+ is(promptState.butt0Disabled, true, "Checking accept-button is disabled");
+ is(promptState.butt1Disabled, false, "Checking cancel-button isn't disabled");
+ }
+
+ is(promptState.defButton0, expectedState.defButton == "button0", "checking button0 default");
+ is(promptState.defButton1, expectedState.defButton == "button1", "checking button1 default");
+ is(promptState.defButton2, expectedState.defButton == "button2", "checking button2 default");
+
+ if (isOSX && expectedState.focused && expectedState.focused.startsWith("button")) {
+ is(promptState.focused, "infoBody", "buttons don't focus on OS X, but infoBody does instead");
+ } else {
+ is(promptState.focused, expectedState.focused, "Checking focused element");
+ }
+}
+
+function checkEchoedAuthInfo(expectedState, doc) {
+ // The server echos back the HTTP auth info it received.
+ let username = doc.getElementById("user").textContent;
+ let password = doc.getElementById("pass").textContent;
+ let authok = doc.getElementById("ok").textContent;
+
+ is(authok, "PASS", "Checking for successful authentication");
+ is(username, expectedState.user, "Checking for echoed username");
+ is(password, expectedState.pass, "Checking for echoed password");
+}
+
+/**
+ * Create a Proxy to relay method calls on an nsIAuthPrompt[2] prompter to a chrome script which can
+ * perform the calls in the parent. Out and inout params will be copied back from the parent to
+ * content.
+ *
+ * @param chromeScript The reference to the chrome script that will listen to `proxyPrompter`
+ * messages in the parent and call the `methodName` method.
+ * The return value from the message handler should be an object with properties:
+ * `rv` - containing the return value of the method call.
+ * `args` - containing the array of arguments passed to the method since out or inout ones could have
+ * been modified.
+ */
+function PrompterProxy(chromeScript) {
+ return new Proxy({}, {
+ get(target, prop, receiver) {
+ return (...args) => {
+ // Array of indices of out/inout params to copy from the parent back to the caller.
+ let outParams = [];
+
+ switch (prop) {
+ case "prompt": {
+ outParams = [/* result */ 5];
+ break;
+ }
+ case "promptAuth": {
+ outParams = [];
+ break;
+ }
+ case "promptPassword": {
+ outParams = [/* pwd */ 4];
+ break;
+ }
+ case "promptUsernameAndPassword": {
+ outParams = [/* user */ 4, /* pwd */ 5];
+ break;
+ }
+ default: {
+ throw new Error("Unknown nsIAuthPrompt method");
+ }
+ }
+
+ let result = chromeScript.sendSyncMessage("proxyPrompter", {
+ args,
+ methodName: prop,
+ })[0][0];
+
+ for (let outParam of outParams) {
+ // Copy the out or inout param value over the original
+ args[outParam].value = result.args[outParam].value;
+ }
+
+ if (prop == "promptAuth") {
+ args[2].username = result.args[2].username;
+ args[2].password = result.args[2].password;
+ args[2].domain = result.args[2].domain;
+ }
+
+ return result.rv;
+ };
+ },
+ });
+}
diff --git a/toolkit/components/prompts/test/test_bug619644.html b/toolkit/components/prompts/test/test_bug619644.html
new file mode 100644
index 000000000..9f61eb18b
--- /dev/null
+++ b/toolkit/components/prompts/test/test_bug619644.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=619644
+-->
+<head>
+ <title>Test for Bug 619644</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="prompt_common.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=619644">Mozilla Bug 619644</a>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+// This is a little yucky, but it works
+// The contents of bug619644_inner.html
+const expectedFinalDoc =
+"<head><\/head><body><p>Original content<\/p>\n<script>\n window.opener.postMessage(\"\", \"*\");\n confirm (\"Message\");\n document.write (\"Extra content\");\n window.opener.postMessage(document.documentElement.innerHTML, \"*\");\n<\/script>Extra content<\/body>";
+
+if (!isTabModal) {
+ todo(false, "Test disabled when tab modal prompts are not enabled.");
+} else {
+ inittest();
+}
+
+var promptDone;
+
+function inittest() {
+ window.addEventListener("message", runtest, false);
+ window.open("bug619644_inner.html", "619644");
+
+ SimpleTest.waitForExplicitFinish();
+}
+
+function runtest(e) {
+ window.removeEventListener("message", runtest, false);
+ window.addEventListener("message", checktest, false);
+
+ let state = {
+ msg : "Message",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ };
+ let action = {
+ buttonClick: "ESC",
+ };
+
+ promptDone = handlePrompt(state, action);
+}
+
+function checktest(e) {
+ is(e.data, expectedFinalDoc, "ESC press should not abort document load");
+ e.source.close();
+ promptDone.then(endtest);
+}
+
+function endtest() {
+ info("Ending test");
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/toolkit/components/prompts/test/test_bug620145.html b/toolkit/components/prompts/test/test_bug620145.html
new file mode 100644
index 000000000..bb4470259
--- /dev/null
+++ b/toolkit/components/prompts/test/test_bug620145.html
@@ -0,0 +1,105 @@
+<html>
+<head>
+ <title>Test for Bug 620145</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="prompt_common.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=620145">Mozilla Bug 620145</a>
+<pre id="test">
+</pre>
+
+<div id="text" style="max-width: 100px" onmouseup="openAlert()">
+ This is a short piece of text used for testing that mouse selecting is
+ stopped when an alert appears.
+</div>
+<div id="text2" style="max-width: 100px">
+ This is another short piece of text used for testing that mouse selecting is
+ stopped when an alert appears.
+</div>
+<button id="button" onmouseup="openAlert()">Button</button>
+
+<script class="testbody" type="text/javascript">
+var selectionTest = false;
+
+function openAlert() {
+ info("opening alert...");
+ alert("hello!");
+ info("...alert done.");
+}
+
+add_task(function* runTest() {
+ var state, action;
+ // The <button> in this test's HTML opens a prompt when clicked.
+ // Here we send the events to simulate clicking it.
+ info("isTabModal? " + isTabModal);
+ selectionTest = isTabModal;
+
+ state = {
+ msg : "hello!",
+ iconClass : "alert-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "ok",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ var button = $("button");
+ dispatchMouseEvent(button, "mousedown");
+ dispatchMouseEvent(button, "mouseup");
+ // alert appears at this point, to be closed by the chrome script.
+
+ yield promptDone;
+ checkSelection();
+
+ // using same state and action.
+ promptDone = handlePrompt(state, action);
+
+ var text = $("text");
+ dispatchMouseEvent(text, "mousedown");
+ dispatchMouseEvent(text, "mouseup");
+ // alert appears at this point, to be closed by the chrome script.
+
+ yield promptDone;
+ checkSelection();
+});
+
+function dispatchMouseEvent(target, type)
+{
+ var win = target.ownerDocument.defaultView;
+ e = document.createEvent("MouseEvent");
+ e.initEvent(type, false, false, win, 0, 1, 1, 1, 1,
+ false, false, false, false, 0, null);
+ var utils = SpecialPowers.getDOMWindowUtils(win);
+ utils.dispatchDOMEventViaPresShell(target, e, true);
+ ok(true, type + " sent to " + target.id);
+}
+
+function checkSelection()
+{
+ if (!selectionTest) {
+ todo(false, "Selection test is disabled when tab modal prompts are not enabled.");
+ } else {
+ synthesizeMouse($("text"), 25, 55, { type: "mousemove" });
+ is(window.getSelection().toString(), "", "selection not made");
+ }
+}
+</script>
+
+</body>
+</html>
diff --git a/toolkit/components/prompts/test/test_dom_prompts.html b/toolkit/components/prompts/test/test_dom_prompts.html
new file mode 100644
index 000000000..413ed8fd5
--- /dev/null
+++ b/toolkit/components/prompts/test/test_dom_prompts.html
@@ -0,0 +1,208 @@
+<html>
+<head>
+ <title>Test for DOM prompts</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="prompt_common.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="text/javascript">
+var rv;
+var state, action;
+
+add_task(function* test_alert_ok() {
+ info("Starting test: Alert");
+ state = {
+ msg : "This is the alert text.",
+ iconClass : "alert-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "ok",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ alert("This is the alert text.");
+
+ yield promptDone;
+});
+
+// bug 861605 made the arguments to alert/confirm optional (prompt already was).
+add_task(function* test_alert_noargs() {
+ info("Starting test: Alert with no args");
+ state = {
+ msg : "",
+ iconClass : "alert-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "ok",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ try {
+ alert();
+ ok(true, "alert() without arguments should not throw!");
+ } catch (e) {
+ ok(false, "alert() without arguments should not throw!");
+ }
+
+ yield promptDone;
+});
+
+
+add_task(function* test_confirm_ok() {
+ info("Starting test: Confirm");
+ state = {
+ msg : "This is the confirm text.",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "ok",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ rv = confirm("This is the confirm text.");
+ is(rv, true, "check prompt return value");
+
+ yield promptDone;
+});
+
+// bug 861605 made the arguments to alert/confirm optional (prompt already was).
+add_task(function* test_confirm_noargs() {
+ info("Starting test: Confirm with no args");
+ state = {
+ msg : "",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "ok",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ try {
+ rv = confirm();
+ ok(true, "confirm() without arguments should not throw!");
+ } catch (e) {
+ ok(false, "confirm() without arguments should not throw!");
+ }
+ is(rv, true, "check prompt return value");
+
+ yield promptDone;
+});
+
+
+add_task(function* test_prompt_ok() {
+ info("Starting test: Prompt");
+ state = {
+ msg : "This is the Prompt text.",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : false,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "textField",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "ok",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ rv = prompt("This is the Prompt text.");
+ is(rv, "", "check prompt return value");
+
+ yield promptDone;
+});
+
+// bug 861605 made the arguments to alert/confirm optional (prompt already was).
+add_task(function* test_prompt_noargs() {
+ info("Starting test: Prompt with no args");
+ state = {
+ msg : "",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : false,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "textField",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "ok",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ try {
+ rv = prompt();
+ ok(true, "prompt() without arguments should not throw!");
+ } catch (e) {
+ ok(false, "prompt() without arguments should not throw!");
+ }
+ is(rv, "", "check prompt return value");
+
+ yield promptDone;
+});
+
+</script>
+
+</body>
+</html>
diff --git a/toolkit/components/prompts/test/test_modal_prompts.html b/toolkit/components/prompts/test/test_modal_prompts.html
new file mode 100644
index 000000000..42e6be52c
--- /dev/null
+++ b/toolkit/components/prompts/test/test_modal_prompts.html
@@ -0,0 +1,1184 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Modal Prompts Test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <script type="text/javascript" src="prompt_common.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+Prompter tests: modal prompts
+<p id="display"></p>
+
+<div id="content" style="display: none">
+ <iframe id="iframe"></iframe>
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.8">
+
+function* runTests() {
+ const { NetUtil } = SpecialPowers.Cu.import('resource://gre/modules/NetUtil.jsm');
+ let state, action;
+ ok(true, "Running tests (isTabModal=" + isTabModal + ", usePromptService=" + usePromptService + ")");
+
+ let prompter, promptArgs;
+ if (usePromptService) {
+ prompter = Cc["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Ci.nsIPromptService2);
+ } else {
+ prompter = Cc["@mozilla.org/prompter;1"].
+ getService(Ci.nsIPromptFactory).
+ getPrompt(window, Ci.nsIPrompt);
+ if (isTabModal) {
+ let bag = prompter.QueryInterface(Ci.nsIWritablePropertyBag2);
+ bag.setPropertyAsBool("allowTabModal", true);
+ }
+ }
+
+ let checkVal = {};
+ let textVal = {};
+ let passVal = {};
+ let flags;
+ let isOK, clickedButton;
+
+ // =====
+ info("Starting test: Alert");
+ state = {
+ msg : "This is the alert text.",
+ title : "TestTitle",
+ iconClass : "alert-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "ok",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ promptArgs = ["TestTitle", "This is the alert text."];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ prompter.alert.apply(null, promptArgs);
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: AlertCheck (null checkbox label, so it's hidden)");
+ state = {
+ msg : "This is the alertCheck text.",
+ title : "TestTitle",
+ iconClass : "alert-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "ok",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ promptArgs = ["TestTitle", "This is the alertCheck text.", null, {}];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ prompter.alertCheck.apply(null, promptArgs);
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: AlertCheck");
+ state = {
+ msg : "This is the alertCheck text.",
+ title : "TestTitle",
+ iconClass : "alert-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : false,
+ textValue : "",
+ passValue : "",
+ checkMsg : "Check me out!",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "ok",
+ setCheckbox: true,
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ checkVal.value = false;
+ promptArgs = ["TestTitle", "This is the alertCheck text.", "Check me out!", checkVal];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ prompter.alertCheck.apply(null, promptArgs);
+ is(checkVal.value, true, "checkbox was checked");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: Confirm (ok)");
+ state = {
+ msg : "This is the confirm text.",
+ title : "TestTitle",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "ok",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ promptArgs = ["TestTitle", "This is the confirm text."];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ isOK = prompter.confirm.apply(null, promptArgs);
+ is(isOK, true, "checked expected retval");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: Confirm (cancel)");
+ state = {
+ msg : "This is the confirm text.",
+ title : "TestTitle",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "cancel",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ promptArgs = ["TestTitle", "This is the confirm text."];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ isOK = prompter.confirm.apply(null, promptArgs);
+ is(isOK, false, "checked expected retval");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: ConfirmCheck (ok, null checkbox label)");
+ state = {
+ msg : "This is the confirmCheck text.",
+ title : "TestTitle",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "ok",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ promptArgs = ["TestTitle", "This is the confirmCheck text.", null, {}];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ isOK = prompter.confirmCheck.apply(null, promptArgs);
+ is(isOK, true, "checked expected retval");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: ConfirmCheck (cancel, null checkbox label)");
+ state = {
+ msg : "This is the confirmCheck text.",
+ title : "TestTitle",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "cancel",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ promptArgs = ["TestTitle", "This is the confirmCheck text.", null, {}];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ isOK = prompter.confirmCheck.apply(null, promptArgs);
+ is(isOK, false, "checked expected retval");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: ConfirmCheck (ok)");
+ state = {
+ msg : "This is the confirmCheck text.",
+ title : "TestTitle",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : false,
+ textValue : "",
+ passValue : "",
+ checkMsg : "Check me out!",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "ok",
+ setCheckbox: true,
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ checkVal.value = false;
+ promptArgs = ["TestTitle", "This is the confirmCheck text.", "Check me out!", checkVal];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ isOK = prompter.confirmCheck.apply(null, promptArgs);
+ is(isOK, true, "checked expected retval");
+ is(checkVal.value, true, "expected checkbox setting");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: ConfirmCheck (cancel)");
+ state = {
+ msg : "This is the confirmCheck text.",
+ title : "TestTitle",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : false,
+ textValue : "",
+ passValue : "",
+ checkMsg : "Check me out!",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "cancel",
+ setCheckbox: true,
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ checkVal.value = false;
+ promptArgs = ["TestTitle", "This is the confirmCheck text.", "Check me out!", checkVal];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ isOK = prompter.confirmCheck.apply(null, promptArgs);
+ is(isOK, false, "checked expected retval");
+ is(checkVal.value, true, "expected checkbox setting");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: Prompt (ok, no default text)");
+ state = {
+ msg : "This is the prompt text.",
+ title : "TestTitle",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : false,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "textField",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick : "ok",
+ textField : "bacon",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ textVal.value = "";
+ promptArgs = ["TestTitle", "This is the prompt text.", textVal, null, {}];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ isOK = prompter.prompt.apply(null, promptArgs);
+ is(isOK, true, "checked expected retval");
+ is(textVal.value, "bacon", "checking expected text value");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: Prompt (ok, default text)");
+ state = {
+ msg : "This is the prompt text.",
+ title : "TestTitle",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : false,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "kittens",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "textField",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "ok",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ textVal.value = "kittens";
+ promptArgs = ["TestTitle", "This is the prompt text.", textVal, null, {}];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ isOK = prompter.prompt.apply(null, promptArgs);
+ is(isOK, true, "checked expected retval");
+ is(textVal.value, "kittens", "checking expected text value");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: Prompt (cancel, default text)");
+ state = {
+ msg : "This is the prompt text.",
+ title : "TestTitle",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : false,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "puppies",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "textField",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "cancel",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ textVal.value = "puppies";
+ promptArgs = ["TestTitle", "This is the prompt text.", textVal, null, {}];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ isOK = prompter.prompt.apply(null, promptArgs);
+ is(isOK, false, "checked expected retval");
+ is(textVal.value, "puppies", "checking expected text value");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: Prompt (cancel, default text modified)");
+ state = {
+ msg : "This is the prompt text.",
+ title : "TestTitle",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : false,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "puppies",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "textField",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick : "cancel",
+ textField : "bacon",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ textVal.value = "puppies";
+ promptArgs = ["TestTitle", "This is the prompt text.", textVal, null, {}];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ isOK = prompter.prompt.apply(null, promptArgs);
+ is(isOK, false, "checked expected retval");
+ is(textVal.value, "puppies", "checking expected text value");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: Prompt (ok, with checkbox)");
+ state = {
+ msg : "This is the prompt text.",
+ title : "TestTitle",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : false,
+ passHidden : true,
+ checkHidden : false,
+ textValue : "tribbles",
+ passValue : "",
+ checkMsg : "Check me out!",
+ checked : false,
+ focused : "textField",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "ok",
+ setCheckbox: true,
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ textVal.value = "tribbles";
+ checkVal.value = false;
+ promptArgs = ["TestTitle", "This is the prompt text.", textVal, "Check me out!", checkVal];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ isOK = prompter.prompt.apply(null, promptArgs);
+ is(isOK, true, "checked expected retval");
+ is(textVal.value, "tribbles", "checking expected text value");
+ is(checkVal.value, true, "expected checkbox setting");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: Prompt (cancel, with checkbox)");
+ state = {
+ msg : "This is the prompt text.",
+ title : "TestTitle",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : false,
+ passHidden : true,
+ checkHidden : false,
+ textValue : "tribbles",
+ passValue : "",
+ checkMsg : "Check me out!",
+ checked : false,
+ focused : "textField",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "cancel",
+ setCheckbox: true,
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ textVal.value = "tribbles";
+ checkVal.value = false;
+ promptArgs = ["TestTitle", "This is the prompt text.", textVal, "Check me out!", checkVal];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ isOK = prompter.prompt.apply(null, promptArgs);
+ is(isOK, false, "checked expected retval");
+ is(textVal.value, "tribbles", "checking expected text value");
+ is(checkVal.value, false, "expected checkbox setting");
+
+ yield promptDone;
+
+ // =====
+ // Just two tests for this, since password manager already tests this extensively.
+ info("Starting test: PromptUsernameAndPassword (ok)");
+ state = {
+ msg : "This is the pUAP text.",
+ title : "TestTitle",
+ iconClass : "authentication-icon question-icon",
+ titleHidden : true,
+ textHidden : false,
+ passHidden : false,
+ checkHidden : false,
+ textValue : "usr",
+ passValue : "ssh",
+ checkMsg : "Check me out!",
+ checked : false,
+ focused : "textField",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "ok",
+ setCheckbox: true,
+ textField: "newusr",
+ passField: "newssh",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ textVal.value = "usr";
+ passVal.value = "ssh";
+ checkVal.value = false;
+ promptArgs = ["TestTitle", "This is the pUAP text.", textVal, passVal, "Check me out!", checkVal];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ isOK = prompter.promptUsernameAndPassword.apply(null, promptArgs);
+ is(isOK, true, "checked expected retval");
+ is(textVal.value, "newusr", "checking expected text value");
+ is(passVal.value, "newssh", "checking expected pass value");
+ is(checkVal.value, true, "expected checkbox setting");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: PromptUsernameAndPassword (cancel)");
+ state = {
+ msg : "This is the pUAP text.",
+ title : "TestTitle",
+ iconClass : "authentication-icon question-icon",
+ titleHidden : true,
+ textHidden : false,
+ passHidden : false,
+ checkHidden : false,
+ textValue : "usr",
+ passValue : "ssh",
+ checkMsg : "Check me out!",
+ checked : false,
+ focused : "textField",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick : "cancel",
+ setCheckbox : true,
+ textField : "newusr",
+ passField : "newssh",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ textVal.value = "usr";
+ passVal.value = "ssh";
+ checkVal.value = false;
+ promptArgs = ["TestTitle", "This is the pUAP text.", textVal, passVal, "Check me out!", checkVal];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ isOK = prompter.promptUsernameAndPassword.apply(null, promptArgs);
+ is(isOK, false, "checked expected retval");
+ is(textVal.value, "usr", "checking expected text value");
+ is(passVal.value, "ssh", "checking expected pass value");
+ is(checkVal.value, false, "expected checkbox setting");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: PromptPassword (ok)");
+ state = {
+ msg : "This is the promptPassword text.",
+ title : "TestTitle",
+ iconClass : "authentication-icon question-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : false,
+ checkHidden : false,
+ textValue : "",
+ passValue : "ssh",
+ checkMsg : "Check me out!",
+ checked : false,
+ focused : "passField",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick : "ok",
+ setCheckbox : true,
+ passField : "newssh",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ passVal.value = "ssh";
+ checkVal.value = false;
+ promptArgs = ["TestTitle", "This is the promptPassword text.", passVal, "Check me out!", checkVal];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ isOK = prompter.promptPassword.apply(null, promptArgs);
+ is(isOK, true, "checked expected retval");
+ is(passVal.value, "newssh", "checking expected pass value");
+ is(checkVal.value, true, "expected checkbox setting");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: PromptPassword (cancel)");
+ state = {
+ msg : "This is the promptPassword text.",
+ title : "TestTitle",
+ iconClass : "authentication-icon question-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : false,
+ checkHidden : false,
+ textValue : "",
+ passValue : "ssh",
+ checkMsg : "Check me out!",
+ checked : false,
+ focused : "passField",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick : "cancel",
+ setCheckbox : true,
+ passField : "newssh",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ passVal.value = "ssh";
+ checkVal.value = false;
+ promptArgs = ["TestTitle", "This is the promptPassword text.", passVal, "Check me out!", checkVal];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ isOK = prompter.promptPassword.apply(null, promptArgs);
+ is(isOK, false, "checked expected retval");
+ is(passVal.value, "ssh", "checking expected pass value");
+ is(checkVal.value, false, "expected checkbox setting");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: ConfirmEx (ok/cancel, ok)");
+ state = {
+ msg : "This is the confirmEx text.",
+ title : "TestTitle",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ butt0Label : "OK",
+ butt1Label : "Cancel",
+ };
+ action = {
+ buttonClick: "ok",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ flags = Ci.nsIPromptService.STD_OK_CANCEL_BUTTONS;
+ promptArgs = ["TestTitle", "This is the confirmEx text.", flags, null, null, null, null, {}];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ clickedButton = prompter.confirmEx.apply(null, promptArgs);
+ is(clickedButton, 0, "checked expected button num click");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: ConfirmEx (yes/no, cancel)");
+ state = {
+ msg : "This is the confirmEx text.",
+ title : "TestTitle",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ butt0Label : "Yes",
+ butt1Label : "No",
+ };
+ action = {
+ buttonClick: "cancel",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ flags = Ci.nsIPromptService.STD_YES_NO_BUTTONS;
+ promptArgs = ["TestTitle", "This is the confirmEx text.", flags, null, null, null, null, {}];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ clickedButton = prompter.confirmEx.apply(null, promptArgs);
+ is(clickedButton, 1, "checked expected button num click");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: ConfirmEx (buttons from args, checkbox, ok)");
+ state = {
+ msg : "This is the confirmEx text.",
+ title : "TestTitle",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : false,
+ textValue : "",
+ passValue : "",
+ checkMsg : "Check me out!",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ butt0Label : "butt0",
+ butt1Label : "butt1",
+ butt2Label : "butt2",
+ };
+ action = {
+ buttonClick: "ok",
+ setCheckbox: true,
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ let b = Ci.nsIPromptService.BUTTON_TITLE_IS_STRING;
+ flags = b * Ci.nsIPromptService.BUTTON_POS_2 +
+ b * Ci.nsIPromptService.BUTTON_POS_1 +
+ b * Ci.nsIPromptService.BUTTON_POS_0;
+ checkVal.value = false;
+ promptArgs = ["TestTitle", "This is the confirmEx text.", flags,
+ "butt0", "butt1", "butt2", "Check me out!", checkVal];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ clickedButton = prompter.confirmEx.apply(null, promptArgs);
+ is(clickedButton, 0, "checked expected button num click");
+ is(checkVal.value, true, "expected checkbox setting");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: ConfirmEx (buttons from args, checkbox, cancel)");
+ state = {
+ msg : "This is the confirmEx text.",
+ title : "TestTitle",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : false,
+ textValue : "",
+ passValue : "",
+ checkMsg : "Check me out!",
+ checked : false,
+ focused : "button1", // Default changed!
+ defButton : "button1",
+ butt0Label : "butt0",
+ butt1Label : "butt1",
+ butt2Label : "butt2",
+ };
+ action = {
+ buttonClick: "cancel",
+ setCheckbox: true,
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ b = Ci.nsIPromptService.BUTTON_TITLE_IS_STRING;
+ flags = b * Ci.nsIPromptService.BUTTON_POS_2 +
+ b * Ci.nsIPromptService.BUTTON_POS_1 +
+ b * Ci.nsIPromptService.BUTTON_POS_0;
+ flags ^= Ci.nsIPromptService.BUTTON_POS_1_DEFAULT;
+ checkVal.value = false;
+ promptArgs = ["TestTitle", "This is the confirmEx text.", flags,
+ "butt0", "butt1", "butt2", "Check me out!", checkVal];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ clickedButton = prompter.confirmEx.apply(null, promptArgs);
+ is(clickedButton, 1, "checked expected button num click");
+ is(checkVal.value, true, "expected checkbox setting");
+
+ yield promptDone;
+
+ // =====
+ info("Starting test: ConfirmEx (buttons from args, checkbox, button3)");
+ state = {
+ msg : "This is the confirmEx text.",
+ title : "TestTitle",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : false,
+ textValue : "",
+ passValue : "",
+ checkMsg : "Check me out!",
+ checked : false,
+ focused : "button2", // Default changed!
+ defButton : "button2",
+ butt0Label : "butt0",
+ butt1Label : "butt1",
+ butt2Label : "butt2",
+ };
+ action = {
+ buttonClick: 2,
+ setCheckbox: true,
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ b = Ci.nsIPromptService.BUTTON_TITLE_IS_STRING;
+ flags = b * Ci.nsIPromptService.BUTTON_POS_2 +
+ b * Ci.nsIPromptService.BUTTON_POS_1 +
+ b * Ci.nsIPromptService.BUTTON_POS_0;
+ flags ^= Ci.nsIPromptService.BUTTON_POS_2_DEFAULT;
+ checkVal.value = false;
+ promptArgs = ["TestTitle", "This is the confirmEx text.", flags,
+ "butt0", "butt1", "butt2", "Check me out!", checkVal];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ clickedButton = prompter.confirmEx.apply(null, promptArgs);
+ is(clickedButton, 2, "checked expected button num click");
+ is(checkVal.value, true, "expected checkbox setting");
+
+ yield promptDone;
+
+ // =====
+ // (skipped for E10S and tabmodal tests: window is required)
+ info("Starting test: Alert, no window");
+ state = {
+ msg : "This is the alert text.",
+ title : "TestTitle",
+ iconClass : "alert-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "ok",
+ };
+ if (!isTabModal && !isE10S) {
+ promptDone = handlePrompt(state, action);
+
+ promptArgs = ["TestTitle", "This is the alert text."];
+ if (usePromptService)
+ promptArgs.unshift(null);
+ prompter.alert.apply(null, promptArgs);
+
+ yield promptDone;
+ }
+
+
+ // =====
+ // (skipped for tabmodal tests: delay not supported)
+ info("Starting test: ConfirmEx (delay, ok)");
+ state = {
+ msg : "This is the confirmEx delay text.",
+ title : "TestTitle",
+ iconClass : "question-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : null, // nothing focused until after delay fires
+ defButton : "button0",
+ butt0Label : "OK",
+ butt1Label : "Cancel",
+ butt0Disabled: true,
+ };
+
+ // OS X doesn't initially focus the button, but rather the infoBody.
+ // The focus stays there even after the button-enable delay has fired.
+ if (isOSX)
+ state.focused = "infoBody";
+
+ action = {
+ buttonClick: "pollOK",
+ };
+ if (!isTabModal) {
+ promptDone = handlePrompt(state, action);
+
+ flags = (Ci.nsIPromptService.STD_OK_CANCEL_BUTTONS | Ci.nsIPromptService.BUTTON_DELAY_ENABLE);
+ promptArgs = ["TestTitle", "This is the confirmEx delay text.", flags, null, null, null, null, {}];
+ if (usePromptService)
+ promptArgs.unshift(window);
+ clickedButton = prompter.confirmEx.apply(null, promptArgs);
+ is(clickedButton, 0, "checked expected button num click");
+
+ yield promptDone;
+ }
+
+ // promptAuth already tested via password manager but do a few specific things here.
+ var channel = NetUtil.newChannel({
+ uri: "http://example.com",
+ loadUsingSystemPrincipal: true
+ });
+
+ var level = Ci.nsIAuthPrompt2.LEVEL_NONE;
+ var authinfo = {
+ username : "",
+ password : "",
+ domain : "",
+ flags : Ci.nsIAuthInformation.AUTH_HOST,
+ authenticationScheme : "basic",
+ realm : ""
+ };
+
+
+ // =====
+ // (promptAuth is only accessible from the prompt service)
+ info("Starting test: promptAuth with empty realm");
+ state = {
+ msg : 'http://example.com is requesting your username and password.',
+ title : "TestTitle",
+ iconClass : "authentication-icon question-icon",
+ titleHidden : true,
+ textHidden : false,
+ passHidden : false,
+ checkHidden : false,
+ textValue : "",
+ passValue : "",
+ checkMsg : "Check me out!",
+ checked : false,
+ focused : "textField",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick : "ok",
+ setCheckbox : true,
+ textField : "username",
+ passField : "password",
+ };
+ if (usePromptService) {
+ promptDone = handlePrompt(state, action);
+
+ checkVal.value = false;
+ isOK = prompter.promptAuth(window, channel, level, authinfo, "Check me out!", checkVal);
+ is(isOK, true, "checked expected retval");
+ is(authinfo.username, "username", "checking filled username");
+ is(authinfo.password, "password", "checking filled password");
+ is(checkVal.value, true, "expected checkbox setting");
+
+ yield promptDone;
+ }
+
+
+ // =====
+ // (promptAuth is only accessible from the prompt service)
+ info("Starting test: promptAuth with long realm");
+ state = {
+ msg : 'http://example.com is requesting your username and password. The site ' +
+ 'says: \u201cabcdefghi abcdefghi abcdefghi abcdefghi abcdefghi abcdefghi abcdefghi ' +
+ 'abcdefghi abcdefghi abcdefghi abcdefghi abcdefghi abcdefghi abcdefghi ' +
+ 'abcdefghi \u2026\u201d',
+ title : "TestTitle",
+ iconClass : "authentication-icon question-icon",
+ titleHidden : true,
+ textHidden : false,
+ passHidden : false,
+ checkHidden : false,
+ textValue : "",
+ passValue : "",
+ checkMsg : "Check me out!",
+ checked : false,
+ focused : "textField",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick : "ok",
+ setCheckbox : true,
+ textField : "username",
+ passField : "password",
+ };
+ if (usePromptService) {
+ promptDone = handlePrompt(state, action);
+
+ checkVal.value = false;
+ var longString = "";
+ for (var i = 0; i < 20; i++)
+ longString += "abcdefghi "; // 200 chars long
+ authinfo.realm = longString;
+ authinfo.username = "";
+ authinfo.password = "";
+ isOK = prompter.promptAuth(window, channel, level, authinfo, "Check me out!", checkVal);
+ is(isOK, true, "checked expected retval");
+ is(authinfo.username, "username", "checking filled username");
+ is(authinfo.password, "password", "checking filled password");
+ is(checkVal.value, true, "expected checkbox setting");
+
+ yield promptDone;
+ }
+
+ info("Starting test: promptAuth for a cross-origin and a empty realm");
+ authinfo = {
+ username : "",
+ password : "",
+ domain : "",
+ flags : Ci. nsIAuthInformation.AUTH_HOST |
+ Ci.nsIAuthInformation.CROSS_ORIGIN_SUB_RESOURCE,
+ authenticationScheme : "basic",
+ realm : ""
+ }
+ state = {
+ msg : 'http://example.com is requesting your username and password. ' +
+ 'WARNING: Your password will not be sent to the website you are currently visiting!',
+ title : "TestTitle",
+ iconClass : "authentication-icon question-icon",
+ titleHidden : true,
+ textHidden : false,
+ passHidden : false,
+ checkHidden : false,
+ textValue : "",
+ passValue : "",
+ checkMsg : "Check me out!",
+ checked : false,
+ focused : "textField",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick : "ok",
+ setCheckbox : false,
+ textField : "username",
+ passField : "password",
+ };
+ if (usePromptService) {
+ promptDone = handlePrompt(state, action);
+ checkVal.value = false;
+ isOK = prompter.promptAuth(window, channel, level, authinfo, "Check me out!", checkVal);
+ is(isOK, true, "checked expected retval");
+ is(authinfo.username, "username", "checking filled username");
+ is(authinfo.password, "password", "checking filled password");
+ is(checkVal.value, false, "expected checkbox setting");
+
+ yield promptDone;
+ }
+
+ info("Starting test: promptAuth for a cross-origin with realm");
+ authinfo = {
+ username : "",
+ password : "",
+ domain : "",
+ flags : Ci. nsIAuthInformation.AUTH_HOST | Ci.nsIAuthInformation.CROSS_ORIGIN_SUB_RESOURCE,
+ authenticationScheme : "basic",
+ realm : "Something!!!"
+ }
+ state = {
+ msg : 'http://example.com is requesting your username and password. ' +
+ 'WARNING: Your password will not be sent to the website you are currently visiting!',
+ title : "TestTitle",
+ iconClass : "authentication-icon question-icon",
+ titleHidden : true,
+ textHidden : false,
+ passHidden : false,
+ checkHidden : false,
+ textValue : "",
+ passValue : "",
+ checkMsg : "Check me out!",
+ checked : false,
+ focused : "textField",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick : "ok",
+ setCheckbox : false,
+ textField : "username",
+ passField : "password",
+ };
+ if (usePromptService) {
+ promptDone = handlePrompt(state, action);
+
+ checkVal.value = false;
+ isOK = prompter.promptAuth(window, channel, level, authinfo, "Check me out!", checkVal);
+ is(isOK, true, "checked expected retval");
+ is(authinfo.username, "username", "checking filled username");
+ is(authinfo.password, "password", "checking filled password");
+ is(checkVal.value, false, "expected checkbox setting");
+
+ yield promptDone;
+ }
+}
+
+let usePromptService;
+
+/*
+ * Run the body of the 3 times:
+ * - 1st pass: with window-modal prompts, using nsIPromptService
+ * - 2nd pass: still window-modal, using nsIPrompt directly (via nsIPromptFactory)
+ * - 3rd pass: with tab-modal prompts. Can't opt into these via * nsIPromptService.
+ */
+
+add_task(function* runPromptTests() {
+ info("Process type: " + SpecialPowers.Services.appinfo.processType);
+
+ isTabModal = false; usePromptService = true;
+ info("Running tests with: isTabModal=" + isTabModal + ", usePromptService=" + usePromptService);
+ yield* runTests();
+
+ isTabModal = false; usePromptService = false;
+ info("Running tests with: isTabModal=" + isTabModal + ", usePromptService=" + usePromptService);
+ yield* runTests();
+
+ if (SpecialPowers.getBoolPref("prompts.tab_modal.enabled")) {
+ isTabModal = true; usePromptService = false;
+ info("Running tests with: isTabModal=" + isTabModal + ", usePromptService=" + usePromptService);
+ yield* runTests();
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/toolkit/components/prompts/test/test_modal_select.html b/toolkit/components/prompts/test/test_modal_select.html
new file mode 100644
index 000000000..1e008d0f4
--- /dev/null
+++ b/toolkit/components/prompts/test/test_modal_select.html
@@ -0,0 +1,146 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Modal Prompts Test</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <script type="text/javascript" src="prompt_common.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+Prompter tests: modal prompts
+<p id="display"></p>
+
+<div id="content" style="display: none">
+ <iframe id="iframe"></iframe>
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.8">
+
+let prompter = Cc["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Ci.nsIPromptService2);
+
+function checkPromptState(promptState, expectedState) {
+ // XXX check title? OS X has title in content
+ // XXX check focused element
+ // XXX check button labels?
+
+ is(promptState.msg, expectedState.msg, "Checking expected message");
+
+ // Compare listbox contents
+ is(promptState.items.length, expectedState.items.length, "Checking listbox length");
+
+ if (promptState.items.length)
+ is(promptState.selectedIndex, 0, "Checking selected index");
+
+ for (let i = 0; i < promptState.items; i++) {
+ is(promptState.items[i], expectedState.items[i], "Checking list item #" + i);
+ }
+}
+
+let selectVal = {};
+let isOK;
+let state, action;
+
+function handlePrompt(state, action) {
+ return new Promise(resolve => {
+ gChromeScript.addMessageListener("promptHandled", function handled(msg) {
+ gChromeScript.removeMessageListener("promptHandled", handled);
+ checkPromptState(msg.promptState, state);
+ resolve(true);
+ });
+ gChromeScript.sendAsyncMessage("handlePrompt", { action: action, isSelect: true});
+ });
+}
+
+
+// =====
+add_task(function* test_select_empty_list() {
+ info("Starting test: Select (0 items, ok)");
+ state = {
+ msg : "This is the select text.",
+ title : "TestTitle",
+ items : [],
+ };
+ action = {
+ buttonClick: "ok",
+ };
+ promptDone = handlePrompt(state, action);
+ items = [];
+ selectVal.value = null; // outparam, just making sure.
+ isOK = prompter.select(window, "TestTitle", "This is the select text.", items.length, items, selectVal);
+ is(isOK, true, "checked expected retval");
+ is(selectVal.value, -1, "checking selected index");
+
+ yield promptDone;
+});
+
+// =====
+add_task(function* test_select_ok() {
+ info("Starting test: Select (3 items, ok)");
+ state = {
+ msg : "This is the select text.",
+ title : "TestTitle",
+ items : ["one", "two", "three"],
+ };
+ action = {
+ buttonClick: "ok",
+ };
+ promptDone = handlePrompt(state, action);
+ items = ["one", "two", "three"];
+ selectVal.value = null; // outparam, just making sure.
+ isOK = prompter.select(window, "TestTitle", "This is the select text.", items.length, items, selectVal);
+ is(isOK, true, "checked expected retval");
+ is(selectVal.value, 0, "checking selected index");
+
+ yield promptDone;
+});
+
+// =====
+add_task(function* test_select_item() {
+ info("Starting test: Select (3 items, selection changed, ok)");
+ state = {
+ msg : "This is the select text.",
+ title : "TestTitle",
+ items : ["one", "two", "three"],
+ };
+ action = {
+ buttonClick: "ok",
+ selectItem: 1,
+ };
+ promptDone = handlePrompt(state, action);
+ items = ["one", "two", "three"];
+ selectVal.value = null; // outparam, just making sure.
+ isOK = prompter.select(window, "TestTitle", "This is the select text.", items.length, items, selectVal);
+ is(isOK, true, "checked expected retval");
+ is(selectVal.value, 1, "checking selected index");
+
+ yield promptDone;
+});
+
+// =====
+add_task(function* test_cancel_prompt() {
+ info("Starting test: Select (3 items, cancel)");
+ state = {
+ msg : "This is the select text.",
+ title : "TestTitle",
+ items : ["one", "two", "three"],
+ };
+ action = {
+ buttonClick: "cancel",
+ };
+ promptDone = handlePrompt(state, action);
+ items = ["one", "two", "three"];
+ selectVal.value = null; // outparam, just making sure.
+ isOK = prompter.select(window, "TestTitle", "This is the select text.", items.length, items, selectVal);
+ is(isOK, false, "checked expected retval");
+ is(selectVal.value, 0, "checking selected index");
+
+ yield promptDone;
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/toolkit/components/prompts/test/test_subresources_prompts.html b/toolkit/components/prompts/test/test_subresources_prompts.html
new file mode 100644
index 000000000..241ce430f
--- /dev/null
+++ b/toolkit/components/prompts/test/test_subresources_prompts.html
@@ -0,0 +1,202 @@
+<html>
+<head>
+ <title>Test subresources prompts (Bug 625187 and bug 1230462)</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="prompt_common.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+<!--
+ - Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/
+ -
+ - Contributor(s):
+ - Mihai Sucan <mihai.sucan@gmail.com>
+ -->
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=625187">Mozilla Bug 625187</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1230462">Mozilla Bug 1230462</a>
+
+<p><button onclick="alert('hello world')">Button</button></p>
+
+<iframe id="iframe_diff_origin" src="http://example.com/tests/toolkit/components/prompts/test/bug625187_iframe.html"></iframe>
+
+<iframe id="iframe_same_origin" src="bug625187_iframe.html"></iframe>
+
+<iframe id="iframe_prompt"></iframe>
+
+<pre id="test"></pre>
+
+<script class="testbody" type="text/javascript">
+var iframe1Loaded = onloadPromiseFor("iframe_diff_origin");
+var iframe2Loaded = onloadPromiseFor("iframe_same_origin");
+var iframe_prompt = document.getElementById("iframe_prompt");
+
+add_task(function* runTest()
+{
+ // This test depends on tab modal prompts being enabled.
+ if (!isTabModal) {
+ todo(false, "Test disabled when tab modal prompts are not enabled.");
+ return;
+ }
+
+ info("Ensuring iframe1 has loaded...");
+ yield iframe1Loaded;
+ info("Ensuring iframe2 has loaded...");
+ yield iframe2Loaded;
+ let state, action;
+
+ state = {
+ msg : "hello world",
+ iconClass : "alert-icon",
+ titleHidden : true,
+ textHidden : true,
+ passHidden : true,
+ checkHidden : true,
+ textValue : "",
+ passValue : "",
+ checkMsg : "",
+ checked : false,
+ focused : "button0",
+ defButton : "button0",
+ };
+ action = {
+ buttonClick: "ok",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ var button = document.querySelector("button");
+ dispatchMouseEvent(button, "click");
+
+ yield promptDone;
+
+
+ // mostly reusing same state/action
+ state.titleHidden = false;
+ state.msg = "hello world 2";
+ promptDone = handlePrompt(state, action);
+
+ var iframe = document.getElementById("iframe_diff_origin");
+ button = SpecialPowers.wrap(iframe.contentWindow).document.getElementById("btn1");
+ dispatchMouseEvent(button, "click");
+
+ yield promptDone;
+
+
+ // mostly reusing same state/action
+ state.titleHidden = true;
+ state.msg = "hello world 2";
+ promptDone = handlePrompt(state, action);
+
+ iframe = document.getElementById("iframe_same_origin");
+ button = iframe.contentWindow.document.getElementById("btn1");
+ dispatchMouseEvent(button, "click");
+
+ yield promptDone;
+
+
+ // mostly reusing same state/action
+ state.msg = "hello world 3";
+ promptDone = handlePrompt(state, action);
+
+ button = iframe.contentWindow.document.getElementById("btn2");
+ dispatchMouseEvent(button, "click");
+
+ yield promptDone;
+});
+
+add_task(function* runTestAuth()
+{
+ // Following tests chack prompt message for a cross-origin and not
+ // cross-origin subresources load
+
+ // Force parent to not look for tab-modal prompts, as they're not
+ // used for auth prompts.
+ isTabModal = false;
+
+ state = {
+ msg : "http://mochi.test:8888 is requesting your username " +
+ "and password. The site says: “mochitest”",
+ title : "Authentication Required",
+ textValue : "",
+ passValue : "",
+ iconClass : "authentication-icon question-icon",
+ titleHidden : true,
+ textHidden : false,
+ passHidden : false,
+ checkHidden : true,
+ checkMsg : "",
+ checked : false,
+ focused : "textField",
+ defButton : "button0",
+ };
+
+ action = {
+ buttonClick : "ok",
+ setCheckbox : false,
+ textField : "mochiuser1",
+ passField : "mochipass1",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ var iframe3Loaded = onloadPromiseFor("iframe_prompt");
+ iframe_prompt.src = "authenticate.sjs?user=mochiuser1&pass=mochipass1";
+ yield promptDone;
+ yield iframe3Loaded;
+ checkEchoedAuthInfo({user: "mochiuser1", pass: "mochipass1"},
+ iframe_prompt.contentDocument);
+
+ // Cross-origin subresourse test.
+
+ // Force parent to not look for tab-modal prompts, as they're not used for auth prompts.
+ isTabModal =false;
+ state = {
+ msg : "http://example.com is requesting your username and password. " +
+ "WARNING: Your password will not be sent to the website you are currently visiting!",
+ title : "Authentication Required",
+ textValue : "",
+ passValue : "",
+ iconClass : "authentication-icon question-icon",
+ titleHidden : true,
+ textHidden : false,
+ passHidden : false,
+ checkHidden : true,
+ checkMsg : "",
+ checked : false,
+ focused : "textField",
+ defButton : "button0",
+ };
+
+ action = {
+ buttonClick : "ok",
+ setCheckbox : false,
+ textField : "mochiuser2",
+ passField : "mochipass2",
+ };
+
+ promptDone = handlePrompt(state, action);
+
+ iframe3Loaded = onloadPromiseFor("iframe_prompt");
+ iframe_prompt.src = "http://example.com/tests/toolkit/components/prompts/test/authenticate.sjs?user=mochiuser2&pass=mochipass2&realm=mochitest";
+ yield promptDone;
+ yield iframe3Loaded;
+ checkEchoedAuthInfo({user: "mochiuser2", pass: "mochipass2"},
+ SpecialPowers.wrap(iframe_prompt.contentWindow).document);
+});
+
+function dispatchMouseEvent(target, type)
+{
+ var win = SpecialPowers.unwrap(target.ownerDocument.defaultView);
+ var e = document.createEvent("MouseEvent");
+ e.initEvent(type, false, false, win, 0, 1, 1, 1, 1,
+ false, false, false, false, 0, null);
+ var utils = SpecialPowers.getDOMWindowUtils(win);
+ utils.dispatchDOMEventViaPresShell(SpecialPowers.unwrap(target), e, true);
+}
+</script>
+</body>
+</html>