Bug 59314 - Alerts should be content-modal, not window-modal. r=gavin, a=blocking+

This commit is contained in:
Justin Dolske 2010-11-19 21:23:25 -08:00
Родитель d03ec40a8f
Коммит e131dbf0f7
13 изменённых файлов: 476 добавлений и 13 удалений

Просмотреть файл

@ -7438,6 +7438,13 @@ function getNotificationBox(aWindow) {
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;

Просмотреть файл

@ -290,6 +290,58 @@
</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"/>

Просмотреть файл

@ -63,6 +63,7 @@ function commonDialogOnLoad() {
let dialog = document.documentElement;
let ui = {
prompt : window,
loginContainer : document.getElementById("loginContainer"),
loginTextbox : document.getElementById("loginTextbox"),
loginLabel : document.getElementById("loginLabel"),

Просмотреть файл

@ -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>

Просмотреть файл

@ -4,3 +4,4 @@ toolkit.jar:
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)

Просмотреть файл

@ -193,10 +193,11 @@ CommonDialog.prototype = {
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();
@ -223,11 +224,10 @@ CommonDialog.prototype = {
}
} 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) {
@ -315,6 +315,7 @@ CommonDialog.prototype = {
},
onButton0 : function() {
this.args.promptActive = false;
this.args.ok = true;
this.args.buttonNumClicked = 0;
@ -337,14 +338,23 @@ CommonDialog.prototype = {
},
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;
},
};

Просмотреть файл

@ -341,6 +341,35 @@ let PromptUtils = {
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 () {
@ -363,11 +392,12 @@ XPCOMUtils.defineLazyGetter(PromptUtils, "ellipsis", function () {
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
@ -376,11 +406,58 @@ function openModalWindow(domWin, uri, args) {
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]),
@ -389,6 +466,17 @@ ModalPrompter.prototype = {
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";

Просмотреть файл

@ -71,11 +71,11 @@ function getTabModalPromptBox(domWin) {
}
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)

Просмотреть файл

@ -1179,3 +1179,8 @@ findbar {
filefield {
-moz-binding: url("chrome://global/content/bindings/filefield.xml#filefield");
}
/*********** tabmodalprompt ************/
tabmodalprompt {
-moz-binding: url("chrome://global/content/tabprompts.xml#tabmodalprompt");
}

Просмотреть файл

@ -42,6 +42,7 @@ toolkit.jar:
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

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -49,6 +49,7 @@ toolkit.jar:
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
@ -221,6 +222,7 @@ toolkit.jar:
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)

Просмотреть файл

@ -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;
}