Bug 59314 - Alerts should be content-modal, not window-modal. r=gavin, a=blocking+
authorJustin Dolske <dolske@mozilla.com>
Fri, 19 Nov 2010 21:23:25 -0800
changeset 57936 b48b84055237eb21efcb0dde3c8d1233f49ce774
parent 57935 b89d1824a762fa63c8abb02d3bedcb7844b6348b
child 57937 1fc05b5edd027de780e55957f13196dfb659e25f
push idunknown
push userunknown
push dateunknown
reviewersgavin, blocking
bugs59314
milestone2.0b8pre
Bug 59314 - Alerts should be content-modal, not window-modal. r=gavin, a=blocking+
browser/base/content/browser.js
browser/base/content/tabbrowser.xml
toolkit/components/prompts/content/commonDialog.js
toolkit/components/prompts/content/tabprompts.xml
toolkit/components/prompts/jar.mn
toolkit/components/prompts/src/CommonDialog.jsm
toolkit/components/prompts/src/nsPrompter.js
toolkit/components/prompts/test/prompt_common.js
toolkit/content/xul.css
toolkit/themes/pinstripe/global/jar.mn
toolkit/themes/pinstripe/global/tabprompts.css
toolkit/themes/winstripe/global/jar.mn
toolkit/themes/winstripe/global/tabprompts.css
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7433,16 +7433,23 @@ let DownloadMonitorPanel = {
 
 function getNotificationBox(aWindow) {
   var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document);
   if (foundBrowser)
     return gBrowser.getNotificationBox(foundBrowser)
   return null;
 };
 
+function getTabModalPromptBox(aWindow) {
+  var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document);
+  if (foundBrowser)
+    return gBrowser.getTabModalPromptBox(foundBrowser)
+  return null;
+};
+
 /* DEPRECATED */
 function getBrowser() gBrowser;
 function getNavToolbox() gNavToolbox;
 
 let gPrivateBrowsingUI = {
   _privateBrowsingService: null,
   _searchBarValue: null,
   _findBarValue: null,
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -285,16 +285,68 @@
         <parameter name="aBrowser"/>
         <body>
           <![CDATA[
             return (aBrowser || this.mCurrentBrowser).parentNode.parentNode;
           ]]>
         </body>
       </method>
 
+      <method name="getTabModalPromptBox">
+        <parameter name="aBrowser"/>
+        <body>
+          <![CDATA[
+            const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+            let browser = (aBrowser || this.mCurrentBrowser);
+            let stack = browser.parentNode;
+            let self = this;
+
+            let promptBox = {
+              appendPrompt : function(args, onCloseCallback) {
+                let count = browser.getAttribute("tabmodalPromptShowing");
+                if (count)
+                    count = parseInt(count) + 1;
+                else
+                    count = 1;
+                browser.setAttribute("tabmodalPromptShowing", count);
+
+                let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt");
+                stack.appendChild(newPrompt);
+                newPrompt.clientTop; // style flush to assure binding is attached
+
+                let tab = self._getTabForContentWindow(browser.contentWindow);
+                newPrompt.init(args, tab, onCloseCallback);
+                return newPrompt;
+              },
+
+              removePrompt : function(aPrompt) {
+                let count = parseInt(browser.getAttribute("tabmodalPromptShowing"));
+                count--;
+                if (count)
+                    browser.setAttribute("tabmodalPromptShowing", count);
+                else
+                    browser.removeAttribute("tabmodalPromptShowing");
+                stack.removeChild(aPrompt);
+              },
+
+              listPrompts : function(aPrompt) {
+                let prompts = [];
+                let els = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
+                // NodeList --> real JS array
+                for (let i = 0; i < els.length; i++)
+                  prompts.push(els[i]);
+                return prompts;
+              },
+            };
+
+            return promptBox;
+          ]]>
+        </body>
+      </method>
+
       <method name="_callProgressListeners">
         <parameter name="aBrowser"/>
         <parameter name="aMethod"/>
         <parameter name="aArguments"/>
         <parameter name="aCallGlobalListeners"/>
         <parameter name="aCallTabsListeners"/>
         <body><![CDATA[
           var rv = true;
--- a/toolkit/components/prompts/content/commonDialog.js
+++ b/toolkit/components/prompts/content/commonDialog.js
@@ -58,16 +58,17 @@ function commonDialogOnLoad() {
     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"),
new file mode 100644
--- /dev/null
+++ b/toolkit/components/prompts/content/tabprompts.xml
@@ -0,0 +1,238 @@
+<?xml version="1.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/skin/tabprompts.css"/>
+    </resources>
+
+    <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+        <!-- This is based on the guts of commonDialog.xul -->
+        <spacer flex="1"/>
+        <hbox>
+            <spacer flex="1"/>
+            <vbox class="mainContainer">
+                <grid class="topContainer">
+                    <columns>
+                        <column/>
+                        <column flex="1"/>
+                    </columns>
+
+                    <rows>
+                        <row>
+                            <hbox align="start">
+                                <image anonid="info.icon" class="info.icon"/>
+                            </hbox>
+                            <vbox class="infoContainer">
+                                <description anonid="info.title" hidden="true"/>
+                                <description anonid="info.body" class="info.body"/>
+                            </vbox>
+                        </row>
+
+                        <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>
+                    </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" hidden="true"/>
+                    <button anonid="button0" label="&okButton.label;"/>
+                    <button anonid="button2" hidden="true"/>
+                    <button anonid="button1" label="&cancelButton.label;"/>
+#endif
+                </hbox>
+            </vbox>
+            <spacer flex="1"/>
+        </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           : getElement("info.icon"),
+                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);
+        ]]>
+        </constructor>
+
+        <field name="ui"/>
+        <field name="args"/>
+        <field name="linkedTab"/>
+        <field name="onCloseCallback"/>
+        <field name="Dialog"/>
+
+        <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()!
+                linkedTab.addEventListener("TabClose", this, false);
+                window.addEventListener("unload", this, false);
+                this.args.domWindow.addEventListener("pagehide", this, false);
+
+                let tmp = {};
+                Components.utils.import("resource://gre/modules/CommonDialog.jsm", tmp);
+                this.Dialog = new tmp.CommonDialog(args, this.ui);
+                this.Dialog.onLoad(null);
+
+                // TODO: should unhide buttonSpacer on Windows when there are 4 buttons.
+                //       Better yet, just drop support for 4-button dialogs. (bug 609510)
+            ]]>
+            </body>
+        </method>
+
+        <method name="shutdownPrompt">
+            <body>
+            <![CDATA[
+                // remove our event listeners
+                try {
+                    this.linkedTab.removeEventListener("TabClose", this, false);
+                    window.removeEventListener("unload", this, false);
+                    this.args.domWindow.removeEventListener("pagehide", this, false);
+                } catch(e) { }
+                // invoke callback
+                this.onCloseCallback();
+            ]]>
+            </body>
+        </method>
+
+        <method name="handleEvent">
+            <parameter name="aEvent"/>
+            <body>
+            <![CDATA[
+                switch (aEvent.type) {
+                  case "TabClose":
+                  case "unload":
+                  case "pagehide":
+                    this.Dialog.abortPrompt();
+                    this.shutdownPrompt();
+                    break;
+                }
+            ]]>
+            </body>
+        </method>
+
+        <method name="onButtonClick">
+            <parameter name="buttonNum"/>
+            <body>
+            <![CDATA[
+                this.Dialog["onButton" + buttonNum]();
+                this.shutdownPrompt();
+            ]]>
+            </body>
+        </method>
+
+        <method name="onKeyAction">
+            <parameter name="action"/>
+            <parameter name="event"/>
+            <body>
+            <![CDATA[
+                if (event.getPreventDefault())
+                    return;
+
+                if (action == "default") {
+                    let bnum = this.args.defaultButtonNum || 0;
+                    let button = this.ui["button" + bnum];
+                    if (!button.hasAttribute("default"))
+                        return;
+                    this.onButtonClick(button);
+                } else { // action == "cancel"
+                    this.onButtonClick(1); // Cancel button
+                }
+            ]]>
+            </body>
+        </method>
+    </implementation>
+
+    <handlers>
+        <!-- Based on dialog.xml handlers -->
+        <handler event="keypress" keycode="VK_ENTER"
+                 group="system" action="this.onKeyAction('default', event);"/>
+        <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);"/>
+#ifndef XP_MACOSX
+        <handler event="focus" phase="capturing">
+            // Focus shift clears the default button.
+            let bnum = this.args.defaultButtonNum || 0;
+            let button = this.ui["button" + bnum];
+            button.setAttribute("default", event.originalTarget == button);
+        </handler>
+#endif
+    </handlers>
+
+  </binding>
+</bindings>
--- a/toolkit/components/prompts/jar.mn
+++ b/toolkit/components/prompts/jar.mn
@@ -1,6 +1,7 @@
 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.xml              (content/tabprompts.xml)
--- a/toolkit/components/prompts/src/CommonDialog.jsm
+++ b/toolkit/components/prompts/src/CommonDialog.jsm
@@ -188,20 +188,21 @@ CommonDialog.prototype = {
             if (this.args.defaultButtonNum)
                 b = this.args.defaultButtonNum;
             let button = this.ui["button" + b];
             if (xulDialog) {
                 xulDialog.defaultButton = ['accept', 'cancel', 'extra1', 'extra2'][b];
                 let isOSX = ("nsILocalFileMac" in Components.interfaces);
                 if (!isOSX)
                     button.focus();
+            } else {
+                button.setAttribute("default", "true");
+                button.focus();
             }
-            // TODO:
-            // else
-            //     (tabmodal prompts need to set a default button for Enter to act upon)
+
         } else {
             if (this.args.promptType == "promptPassword")
                 this.ui.password1Textbox.select();
             else
                 this.ui.loginTextbox.select();
         }
 
         if (this.args.enableDelay) {
@@ -218,21 +219,20 @@ CommonDialog.prototype = {
         try {
             if (this.soundID) {
                 Cc["@mozilla.org/sound;1"].
                 createInstance(Ci.nsISound).
                 playEventSound(soundID);
             }
         } catch (e) { }
 
-        if (xulDialog)
-            Services.obs.notifyObservers(xulDialog.ownerDocument.defaultView, "common-dialog-loaded", null);
-        // TODO:
-        // else
-        //    (notify using what as the subject?)
+        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.
@@ -310,16 +310,17 @@ CommonDialog.prototype = {
         this.setButtonsEnabledState(true);
     },
 
     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) {
@@ -332,19 +333,28 @@ CommonDialog.prototype = {
             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;
+    },
+
 };
--- a/toolkit/components/prompts/src/nsPrompter.js
+++ b/toolkit/components/prompts/src/nsPrompter.js
@@ -336,16 +336,45 @@ let PromptUtils = {
     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);
     },
+
+    getTabModalPrompt : function (domWin) {
+        var promptBox = null;
+
+        // Given a content DOM window, returns the chrome window it's in.
+        function getChromeWindow(aWindow) {
+            var chromeWin = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                                   .getInterface(Ci.nsIWebNavigation)
+                                   .QueryInterface(Ci.nsIDocShell)
+                                   .chromeEventHandler.ownerDocument.defaultView;
+            return chromeWin;
+        }
+
+        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 = getChromeWindow(promptWin).wrappedJSObject;
+
+            if (chromeWin.getTabModalPromptBox)
+                promptBox = chromeWin.getTabModalPromptBox(promptWin);
+        } catch (e) {
+            // If any errors happen, just assume no tabmodal prompter.
+        }
+
+        return promptBox;
+    },
 };
 
 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!";
@@ -358,42 +387,101 @@ XPCOMUtils.defineLazyGetter(PromptUtils,
         ellipsis = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data;
     } catch (e) { }
     return ellipsis;
 });
 
 
 
 function openModalWindow(domWin, uri, args) {
-    // XXX do we want to do modal state if we fall back to .activeWindow?
+    // XXX Investigate supressing modal state when we're called without a
+    // window? Seems odd to affect whatever window happens to be active.
     if (!domWin)
         domWin = Services.ww.activeWindow;
 
-    // XXX domWin may still be null here if there are _no_ windows open.
+    // 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 OpenWindowJSInternal
     // will do that. Similarly for enterModalState / leaveModalState.
 
     Services.ww.openWindow(domWin, uri, "_blank", "centerscreen,chrome,modal,titlebar", args);
 }
 
+function openTabPrompt(domWin, tabPrompt, args) {
+    let winUtils = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
+                         .getInterface(Ci.nsIDOMWindowUtils);
+    winUtils.enterModalState();
+
+    // 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;
+    function onPromptClose(forceCleanup) {
+        if (!newPrompt && !forceCleanup)
+            return;
+        callbackInvoked = true;
+        if (newPrompt)
+            tabPrompt.removePrompt(newPrompt);
+        winUtils.leaveModalState();
+    }
+
+    let newPrompt;
+    try {
+        // tab-modal prompts need to watch for navigation changes, give it the
+        // domWindow to watch for pagehide events.
+        args.domWindow = domWin;
+        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::OpenWindowJSInternal
+        // 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 ModalPrompter(domWin) {
     this.domWin = domWin;
 }
 ModalPrompter.prototype = {
     domWin : null,
+    allowTabModal : true,
 
     QueryInterface : XPCOMUtils.generateQI([Ci.nsIPrompt, Ci.nsIAuthPrompt, Ci.nsIAuthPrompt2]),
 
 
     /* ---------- internal methods ---------- */
 
 
     openPrompt : function (args) {
+        let allowTabModal = this.allowTabModal;
+
+        if (allowTabModal && this.domWin) {
+            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;
 
         let propBag = PromptUtils.objectToPropBag(args);
         openModalWindow(this.domWin, uri, propBag);
         PromptUtils.propBagToObject(propBag, args);
--- a/toolkit/components/prompts/test/prompt_common.js
+++ b/toolkit/components/prompts/test/prompt_common.js
@@ -66,21 +66,21 @@ function getTabModalPromptBox(domWin) {
         var chromeWin = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                                .getInterface(Ci.nsIWebNavigation)
                                .QueryInterface(Ci.nsIDocShell)
                                .chromeEventHandler.ownerDocument.defaultView;
         return chromeWin;
     }
 
     try {
-        // Get topmost window, in case we're in a frame.
-        var promptWin = domWin.top
+        // 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.
-        // .wrappedJSObject needed here -- see bug 422974 comment 5.
+        // (Unwrap because we need a non-IDL property below.)
         var chromeWin = getChromeWindow(promptWin).wrappedJSObject;
 
         if (chromeWin.getTabModalPromptBox)
             promptBox = chromeWin.getTabModalPromptBox(promptWin);
     } catch (e) {
         // If any errors happen, just assume no tabmodal prompter.
     }
 
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -1174,8 +1174,13 @@ findbar {
   -moz-binding: url("chrome://global/content/bindings/findbar.xml#findbar-textbox");
 }
 
 
 /*********** filefield ************/
 filefield {
   -moz-binding: url("chrome://global/content/bindings/filefield.xml#filefield");
 }
+
+/*********** tabmodalprompt ************/
+tabmodalprompt {
+  -moz-binding: url("chrome://global/content/tabprompts.xml#tabmodalprompt");
+}
--- a/toolkit/themes/pinstripe/global/jar.mn
+++ b/toolkit/themes/pinstripe/global/jar.mn
@@ -37,16 +37,17 @@ toolkit.jar:
   skin/classic/global/radio.css
   skin/classic/global/resizer.css
   skin/classic/global/richlistbox.css
   skin/classic/global/scrollbars.css                                 (nativescrollbars.css)
 * skin/classic/global/scale.css
   skin/classic/global/scrollbox.css
   skin/classic/global/spinbuttons.css
   skin/classic/global/splitter.css
+  skin/classic/global/tabprompts.css
   skin/classic/global/tabbox.css
   skin/classic/global/textbox.css
   skin/classic/global/datetimepicker.css
   skin/classic/global/toolbar.css
   skin/classic/global/toolbarbutton.css
   skin/classic/global/tree.css
 * skin/classic/global/viewbuttons.css
 * skin/classic/global/webConsole.css
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/pinstripe/global/tabprompts.css
@@ -0,0 +1,27 @@
+/* Tab Modal Prompt boxes */
+tabmodalprompt {
+    width: 100%;
+    height: 100%;
+    color: white;
+    background-image: -moz-repeating-linear-gradient(-45deg,
+                                                     rgba(65, 65, 65, 0.8),
+                                                     rgba(65, 65, 65, 0.8) 20px,
+                                                     rgba(69, 69, 69, 0.8) 20px,
+                                                     rgba(69, 69, 69, 0.8) 40px);
+    -moz-box-pack: center;
+    -moz-box-orient: vertical;
+}
+
+.mainContainer {
+    min-width: 20em;
+    padding: 10px;
+    background: rgb(80,80,80);
+    border: 1px solid rgb(20,20,20);
+    border-radius: 12px;
+    box-shadow: inset 0 1px 3.5px rgba(0,0,0,0.8),
+                      0 1px 0 rgba(255,255,255,0.2);
+}
+
+.topContainer {
+    min-height: 64px;
+}
--- a/toolkit/themes/winstripe/global/jar.mn
+++ b/toolkit/themes/winstripe/global/jar.mn
@@ -44,16 +44,17 @@ toolkit.jar:
         skin/classic/global/resizer.css
         skin/classic/global/richlistbox.css
 *       skin/classic/global/scale.css
         skin/classic/global/scrollbars.css                       (xulscrollbars.css)
         skin/classic/global/scrollbox.css
         skin/classic/global/spinbuttons.css
         skin/classic/global/splitter.css
         skin/classic/global/tabbox.css
+        skin/classic/global/tabprompts.css
         skin/classic/global/textbox.css
 *       skin/classic/global/toolbar.css
         skin/classic/global/toolbarbutton.css
         skin/classic/global/tree.css
 *       skin/classic/global/webConsole.css
 *       skin/classic/global/webConsole_networkPanel.css
         skin/classic/global/wizard.css
         skin/classic/global/alerts/alert.css                     (alerts/alert.css)
@@ -216,16 +217,17 @@ toolkit.jar:
         skin/classic/aero/global/resizer.css
         skin/classic/aero/global/richlistbox.css
 *       skin/classic/aero/global/scale.css
         skin/classic/aero/global/scrollbars.css                          (xulscrollbars.css)
         skin/classic/aero/global/scrollbox.css
         skin/classic/aero/global/spinbuttons.css
         skin/classic/aero/global/splitter.css
         skin/classic/aero/global/tabbox.css
+        skin/classic/aero/global/tabprompts.css
 *       skin/classic/aero/global/textbox.css                             (textbox-aero.css)
 *       skin/classic/aero/global/toolbar.css
 *       skin/classic/aero/global/toolbarbutton.css                       (toolbarbutton-aero.css)
 *       skin/classic/aero/global/tree.css                                (tree-aero.css)
 *       skin/classic/aero/global/webConsole.css
 *       skin/classic/aero/global/webConsole_networkPanel.css
         skin/classic/aero/global/wizard.css
         skin/classic/aero/global/alerts/alert.css                        (alerts/alert.css)
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/winstripe/global/tabprompts.css
@@ -0,0 +1,31 @@
+/* Tab Modal Prompt boxes */
+tabmodalprompt {
+    width: 100%;
+    height: 100%;
+    color: white;
+    background-image: -moz-repeating-linear-gradient(-45deg,
+                                                     rgba(65, 65, 65, 0.8),
+                                                     rgba(65, 65, 65, 0.8) 20px,
+                                                     rgba(69, 69, 69, 0.8) 20px,
+                                                     rgba(69, 69, 69, 0.8) 40px);
+    -moz-box-pack: center;
+    -moz-box-orient: vertical;
+}
+
+.mainContainer {
+    min-width: 20em;
+    padding: 10px;
+    background: rgb(80,80,80);
+    border: 1px solid rgb(20,20,20);
+    border-radius: 12px;
+    box-shadow: inset 0 1px 3.5px rgba(0,0,0,0.8),
+                      0 1px 0 rgba(255,255,255,0.2);
+}
+
+.topContainer {
+    min-height: 64px;
+}
+
+.buttonContainer {
+    -moz-box-pack: center;
+}