Bug 76053 Windows mouse integration: no "Snap to default button in dialog boxes" r=enn+ere, sr=roc

This commit is contained in:
Masayuki Nakano 2009-07-15 18:54:30 +09:00
parent 73d238f7a1
commit 4c03c99935
13 changed files with 524 additions and 7 deletions

View File

@ -167,6 +167,8 @@
#include "nsFocusManager.h"
#ifdef MOZ_XUL
#include "nsXULPopupManager.h"
#include "nsIDOMXULControlElement.h"
#include "nsIFrame.h"
#endif
#include "plbase64.h"
@ -3775,6 +3777,19 @@ nsGlobalWindow::GetMainWidget()
return widget;
}
nsIWidget*
nsGlobalWindow::GetNearestWidget()
{
nsIDocShell* docShell = GetDocShell();
NS_ENSURE_TRUE(docShell, nsnull);
nsCOMPtr<nsIPresShell> presShell;
docShell->GetPresShell(getter_AddRefs(presShell));
NS_ENSURE_TRUE(presShell, nsnull);
nsIFrame* rootFrame = presShell->GetRootFrame();
NS_ENSURE_TRUE(rootFrame, nsnull);
return rootFrame->GetView()->GetNearestWidget(nsnull);
}
NS_IMETHODIMP
nsGlobalWindow::SetFullScreen(PRBool aFullScreen)
{
@ -8918,6 +8933,51 @@ nsGlobalChromeWindow::SetBrowserDOMWindow(nsIBrowserDOMWindow *aBrowserWindow)
return NS_OK;
}
NS_IMETHODIMP
nsGlobalChromeWindow::NotifyDefaultButtonLoaded(nsIDOMElement* aDefaultButton)
{
#ifdef MOZ_XUL
NS_ENSURE_ARG(aDefaultButton);
// Don't snap to a disabled button.
nsCOMPtr<nsIDOMXULControlElement> xulControl =
do_QueryInterface(aDefaultButton);
NS_ENSURE_TRUE(xulControl, NS_ERROR_FAILURE);
PRBool disabled;
nsresult rv = xulControl->GetDisabled(&disabled);
NS_ENSURE_SUCCESS(rv, rv);
if (disabled)
return NS_OK;
// Get the button rect in screen coordinates.
nsCOMPtr<nsIContent> content(do_QueryInterface(aDefaultButton));
NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
nsIDocument *doc = content->GetCurrentDoc();
NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
nsIPresShell *shell = doc->GetPrimaryShell();
NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
nsIFrame *frame = shell->GetPrimaryFrameFor(content);
NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
nsIntRect buttonRect = frame->GetScreenRect();
// Get the widget rect in screen coordinates.
nsIWidget *widget = GetNearestWidget();
NS_ENSURE_TRUE(widget, NS_ERROR_FAILURE);
nsIntRect widgetRect;
rv = widget->GetScreenBounds(widgetRect);
NS_ENSURE_SUCCESS(rv, rv);
// Convert the buttonRect coordinates from screen to the widget.
buttonRect -= widgetRect.TopLeft();
rv = widget->OnDefaultButtonLoaded(buttonRect);
if (rv == NS_ERROR_NOT_IMPLEMENTED)
return NS_OK;
return rv;
#else
return NS_ERROR_NOT_IMPLEMENTED;
#endif
}
// nsGlobalModalWindow implementation
// QueryInterface implementation for nsGlobalModalWindow

View File

@ -608,6 +608,7 @@ protected:
PRBool WindowExists(const nsAString& aName, PRBool aLookForCallerOnJSStack);
already_AddRefed<nsIWidget> GetMainWidget();
nsIWidget* GetNearestWidget();
void Freeze()
{

View File

@ -39,8 +39,9 @@
#include "domstubs.idl"
interface nsIBrowserDOMWindow;
interface nsIDOMElement;
[scriptable, uuid(09A5E148-2A77-4739-9DD9-3D552F5390EE)]
[scriptable, uuid(09b86cbd-9784-4fe4-9be6-70b9bbca3a9c)]
interface nsIDOMChromeWindow : nsISupports
{
const unsigned short STATE_MAXIMIZED = 1;
@ -66,4 +67,10 @@ interface nsIDOMChromeWindow : nsISupports
void maximize();
void minimize();
void restore();
/**
* Notify a default button is loaded on a dialog or a wizard.
* defaultButton is the default button.
*/
void notifyDefaultButtonLoaded(in nsIDOMElement defaultButton);
};

View File

@ -97,5 +97,11 @@ else
_TEST_FILES += test_autocomplete.xul
endif
ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
_TEST_FILES += test_cursorsnap.xul \
window_cursorsnap_dialog.xul \
window_cursorsnap_wizard.xul
endif
libs:: $(_TEST_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)

View File

@ -0,0 +1,130 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window title="Cursor snapping test"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js"/>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
<body xmlns="http://www.w3.org/1999/xhtml">
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
<script class="testbody" type="application/javascript">
<![CDATA[
SimpleTest.waitForExplicitFinish();
const kMaxRetryCount = 4;
const kTimeoutTime = [
100, 100, 1000, 1000, 5000
];
var gRetryCount;
var gTestingCount = 0;
var gTestingIndex = -1;
var gDisable = false;
var gHidden = false;
function canRetryTest()
{
return gRetryCount <= kMaxRetryCount;
}
function getTimeoutTime()
{
return kTimeoutTime[gRetryCount];
}
function runNextTest()
{
gRetryCount = 0;
gTestingIndex++;
runCurrentTest();
}
function retryCurrentTest()
{
ok(canRetryTest(), "retry the current test...");
gRetryCount++;
runCurrentTest();
}
function runCurrentTest()
{
var position = "top=" + gTestingCount + ",left=" + gTestingCount + ",";
gTestingCount++;
switch (gTestingIndex) {
case 0:
gDisable = false;
gHidden = false;
window.open("window_cursorsnap_dialog.xul", "_blank",
position + "chrome,width=100,height=100");
break;
case 1:
gDisable = true;
gHidden = false;
window.open("window_cursorsnap_dialog.xul", "_blank",
position + "chrome,width=100,height=100");
break;
case 2:
gDisable = false;
gHidden = true;
window.open("window_cursorsnap_dialog.xul", "_blank",
position + "chrome,width=100,height=100");
break;
case 3:
gDisable = false;
gHidden = false;
window.open("window_cursorsnap_wizard.xul", "_blank",
position + "chrome,width=100,height=100");
break;
case 4:
gDisable = true;
gHidden = false;
window.open("window_cursorsnap_wizard.xul", "_blank",
position + "chrome,width=100,height=100");
break;
case 5:
gDisable = false;
gHidden = true;
window.open("window_cursorsnap_wizard.xul", "_blank",
position + "chrome,width=100,height=100");
break;
default:
SetPrefs(false);
SimpleTest.finish();
return;
}
}
function SetPrefs(aSet)
{
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var prefSvc = Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch2);
const kPrefName = "ui.cursor_snapping.always_enabled";
if (aSet) {
prefSvc.setBoolPref(kPrefName, true);
} else {
prefSvc.clearUserPref(kPrefName);
}
}
SetPrefs(true);
runNextTest();
]]>
</script>
</window>

View File

@ -0,0 +1,104 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<dialog title="Cursor snapping test" id="dialog"
width="600" height="600"
onload="onload();"
onunload="onunload();"
buttons="accept,cancel"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script class="testbody" type="application/javascript">
<![CDATA[
function ok(aCondition, aMessage)
{
window.opener.wrappedJSObject.SimpleTest.ok(aCondition, aMessage);
}
function is(aLeft, aRight, aMessage)
{
window.opener.wrappedJSObject.SimpleTest.is(aLeft, aRight, aMessage);
}
function isnot(aLeft, aRight, aMessage)
{
window.opener.wrappedJSObject.SimpleTest.isnot(aLeft, aRight, aMessage);
}
function canRetryTest()
{
return window.opener.wrappedJSObject.canRetryTest();
}
function getTimeoutTime()
{
return window.opener.wrappedJSObject.getTimeoutTime();
}
var gTimer;
var gRetry;
function finishByTimeout()
{
var button = document.getElementById("dialog").getButton("accept");
if (button.disabled)
ok(true, "cursor is NOT snapped to the disabled button (dialog)");
else if (button.hidden)
ok(true, "cursor is NOT snapped to the hidden button (dialog)");
else {
if (!canRetryTest()) {
ok(false, "cursor is NOT snapped to the default button (dialog)");
} else {
// otherwise, this may be unexpected timeout, we should retry the test.
gRetry = true;
}
}
finish();
}
function finish()
{
window.close();
}
function onMouseMove(aEvent)
{
var button = document.getElementById("dialog").getButton("accept");
if (button.disabled)
ok(false, "cursor IS snapped to the disabled button (dialog)");
else if (button.hidden)
ok(false, "cursor IS snapped to the hidden button (dialog)");
else
ok(true, "cursor IS snapped to the default button (dialog)");
clearTimeout(gTimer);
finish();
}
function onload()
{
var button = document.getElementById("dialog").getButton("accept");
button.addEventListener("mousemove", onMouseMove, false);
if (window.opener.wrappedJSObject.gDisable) {
button.disabled = true;
}
if (window.opener.wrappedJSObject.gHidden) {
button.hidden = true;
}
gRetry = false;
gTimer = setTimeout(finishByTimeout, getTimeoutTime());
}
function onunload()
{
if (gRetry) {
window.opener.wrappedJSObject.retryCurrentTest();
} else {
window.opener.wrappedJSObject.runNextTest();
}
}
]]>
</script>
</dialog>

View File

@ -0,0 +1,111 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<wizard title="Cursor snapping test" id="wizard"
width="600" height="600"
onload="onload();"
onunload="onunload();"
buttons="accept,cancel"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<wizardpage>
<label value="first page"/>
</wizardpage>
<wizardpage>
<label value="second page"/>
</wizardpage>
<script class="testbody" type="application/javascript">
<![CDATA[
function ok(aCondition, aMessage)
{
window.opener.wrappedJSObject.SimpleTest.ok(aCondition, aMessage);
}
function is(aLeft, aRight, aMessage)
{
window.opener.wrappedJSObject.SimpleTest.is(aLeft, aRight, aMessage);
}
function isnot(aLeft, aRight, aMessage)
{
window.opener.wrappedJSObject.SimpleTest.isnot(aLeft, aRight, aMessage);
}
function canRetryTest()
{
return window.opener.wrappedJSObject.canRetryTest();
}
function getTimeoutTime()
{
return window.opener.wrappedJSObject.getTimeoutTime();
}
var gTimer;
var gRetry = false;
function finishByTimeout()
{
var button = document.getElementById("wizard").getButton("next");
if (button.disabled)
ok(true, "cursor is NOT snapped to the disabled button (wizard)");
else if (button.hidden)
ok(true, "cursor is NOT snapped to the hidden button (wizard)");
else {
if (!canRetryTest()) {
ok(false, "cursor is NOT snapped to the default button (wizard)");
} else {
// otherwise, this may be unexpected timeout, we should retry the test.
gRetry = true;
}
}
finish();
}
function finish()
{
window.close();
}
function onMouseMove()
{
var button = document.getElementById("wizard").getButton("next");
if (button.disabled)
ok(false, "cursor IS snapped to the disabled button (wizard)");
else if (button.hidden)
ok(false, "cursor IS snapped to the hidden button (wizard)");
else
ok(true, "cursor IS snapped to the default button (wizard)");
clearTimeout(gTimer);
finish();
}
function onload()
{
var button = document.getElementById("wizard").getButton("next");
button.addEventListener("mousemove", onMouseMove, false);
if (window.opener.wrappedJSObject.gDisable) {
button.disabled = true;
}
if (window.opener.wrappedJSObject.gHidden) {
button.hidden = true;
}
gTimer = setTimeout(finishByTimeout, getTimeoutTime());
}
function onunload()
{
if (gRetry) {
window.opener.wrappedJSObject.retryCurrentTest();
} else {
window.opener.wrappedJSObject.runNextTest();
}
}
]]>
</script>
</wizard>

View File

@ -158,9 +158,11 @@
<body>
<![CDATA[
function focusInit() {
const dialog = document.documentElement;
const defaultButton = dialog.getButton(dialog.defaultButton);
// give focus to the first focusable element in the dialog
if (!document.commandDispatcher.focusedElement) {
document.commandDispatcher.advanceFocusIntoSubtree(document.documentElement);
document.commandDispatcher.advanceFocusIntoSubtree(dialog);
var focusedElt = document.commandDispatcher.focusedElement;
if (focusedElt) {
if (focusedElt.localName == 'tab') {
@ -174,14 +176,17 @@
}
#ifndef XP_MACOSX
else {
const dialog = document.documentElement;
const defaultButton = dialog.getButton(dialog.defaultButton);
if (focusedElt.hasAttribute("dlgtype") && focusedElt != defaultButton)
defaultButton.focus();
}
#endif
}
}
try {
if (defaultButton)
window.notifyDefaultButtonLoaded(defaultButton);
} catch (e) { }
}
// Give focus after onload completes, see bug 103197.

View File

@ -324,6 +324,13 @@
// give focus to the first focusable element in the dialog
if (!document.commandDispatcher.focusedElement)
document.commandDispatcher.advanceFocusIntoSubtree(document.documentElement);
try {
var button =
document.documentElement._wizardButtons.defaultButton;
if (button)
window.notifyDefaultButtonLoaded(button);
} catch (e) { }
};
// Give focus after onload completes, see bug 103197.
@ -581,6 +588,21 @@
}
]]></body>
</method>
<property name="defaultButton" readonly="true">
<getter><![CDATA[
const kXULNS =
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var buttons = this._wizardButtonDeck.selectedPanel
.getElementsByTagNameNS(kXULNS, "button");
for (var i = 0; i < buttons.length; i++) {
if (buttons[i].getAttribute("default") == "true" &&
!buttons[i].hidden && !buttons[i].disabled)
return buttons[i];
}
return null;
]]></getter>
</property>
</implementation>
</binding>
#endif

View File

@ -100,10 +100,10 @@ typedef nsEventStatus (* EVENT_CALLBACK)(nsGUIEvent *event);
#define NS_NATIVE_TSF_DISPLAY_ATTR_MGR 102
#endif
// 3d277f04-93f4-4384-9fdc-e1e2d1fc4e33
// {a395289d-b344-42c3-ae7e-34d64282b6e0}
#define NS_IWIDGET_IID \
{ 0x3d277f04, 0x93f4, 0x4384, \
{ 0x9f, 0xdc, 0xe1, 0xe2, 0xd1, 0xfc, 0x4e, 0x33 } }
{ 0xa395289d, 0xb344, 0x42c3, \
{ 0xae, 0x7e, 0x34, 0xd6, 0x42, 0x82, 0xb6, 0xe0 } }
/*
* Window shadow styles
@ -1064,6 +1064,12 @@ class nsIWidget : public nsISupports {
*/
NS_IMETHOD OnIMESelectionChange(void) = 0;
/*
* Call this method when a dialog is opened which has a default button.
* The button's rectangle should be supplied in aButtonRect.
*/
NS_IMETHOD OnDefaultButtonLoaded(const nsIntRect &aButtonRect) = 0;
protected:
// keep the list of children. We also keep track of our siblings.
// The ownership model is as follows: parent holds a strong ref to

View File

@ -2762,6 +2762,69 @@ gfxASurface *nsWindow::GetThebesSurface()
return (new gfxWindowsSurface(mWnd));
}
/**************************************************************
*
* SECTION: nsIWidget::OnDefaultButtonLoaded
*
* Called after the dialog is loaded and it has a default button.
*
**************************************************************/
NS_IMETHODIMP
nsWindow::OnDefaultButtonLoaded(const nsIntRect &aButtonRect)
{
#ifdef WINCE
return NS_ERROR_NOT_IMPLEMENTED;
#else
if (aButtonRect.IsEmpty())
return NS_OK;
// Don't snap when we are not active.
HWND activeWnd = ::GetActiveWindow();
if (activeWnd != ::GetForegroundWindow() ||
GetTopLevelHWND(mWnd, PR_TRUE) != GetTopLevelHWND(activeWnd, PR_TRUE)) {
return NS_OK;
}
PRBool isAlwaysSnapCursor = PR_FALSE;
nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
if (prefs) {
nsCOMPtr<nsIPrefBranch> prefBranch;
prefs->GetBranch(nsnull, getter_AddRefs(prefBranch));
if (prefBranch) {
prefBranch->GetBoolPref("ui.cursor_snapping.always_enabled",
&isAlwaysSnapCursor);
}
}
if (!isAlwaysSnapCursor) {
BOOL snapDefaultButton;
if (!::SystemParametersInfo(SPI_GETSNAPTODEFBUTTON, 0,
&snapDefaultButton, 0) || !snapDefaultButton)
return NS_OK;
}
nsIntRect widgetRect;
nsresult rv = GetScreenBounds(widgetRect);
NS_ENSURE_SUCCESS(rv, rv);
nsIntRect buttonRect(aButtonRect + widgetRect.TopLeft());
nsIntPoint centerOfButton(buttonRect.x + buttonRect.width / 2,
buttonRect.y + buttonRect.height / 2);
// The center of the button can be outside of the widget.
// E.g., it could be hidden by scrolling.
if (!widgetRect.Contains(centerOfButton)) {
return NS_OK;
}
if (!::SetCursorPos(centerOfButton.x, centerOfButton.y)) {
NS_ERROR("SetCursorPos failed");
return NS_ERROR_FAILURE;
}
return NS_OK;
#endif
}
/**************************************************************
**************************************************************
**

View File

@ -161,6 +161,7 @@ public:
NS_IMETHOD GetAttention(PRInt32 aCycleCount);
virtual PRBool HasPendingInputEvent();
gfxASurface *GetThebesSurface();
NS_IMETHOD OnDefaultButtonLoaded(const nsIntRect &aButtonRect);
virtual nsresult SynthesizeNativeKeyEvent(PRInt32 aNativeKeyboardLayout,
PRInt32 aNativeKeyCode,
PRUint32 aModifierFlags,

View File

@ -141,6 +141,7 @@ public:
NS_IMETHOD OnIMEFocusChange(PRBool aFocus) { return NS_ERROR_NOT_IMPLEMENTED; }
NS_IMETHOD OnIMETextChange(PRUint32 aStart, PRUint32 aOldEnd, PRUint32 aNewEnd) { return NS_ERROR_NOT_IMPLEMENTED; }
NS_IMETHOD OnIMESelectionChange(void) { return NS_ERROR_NOT_IMPLEMENTED; }
NS_IMETHOD OnDefaultButtonLoaded(const nsIntRect &aButtonRect) { return NS_ERROR_NOT_IMPLEMENTED; }
protected: