Bug 1224790 - Use SetFakeModal instead of SetModal for non-modal window opened by modal window. r=smaug, mstange

This commit is contained in:
Tooru Fujisawa 2015-11-18 20:12:26 +09:00
parent 4885ef86f6
commit 7e18b44563
13 changed files with 415 additions and 19 deletions

View File

@ -17,10 +17,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=406375
<pre id="test">
<script type="application/javascript">
if (navigator.platform.startsWith("Mac")) {
SimpleTest.expectAssertions(1);
}
/** Test for Bug 406375 **/

View File

@ -11,6 +11,10 @@ support-files =
file_bug799299.xul
file_bug800817.xul
file_bug830858.xul
file_bug1224790-1_modal.xul
file_bug1224790-1_nonmodal.xul
file_bug1224790-2_modal.xul
file_bug1224790-2_nonmodal.xul
file_subscript_bindings.js
focus_frameset.html
focus_window2.xul
@ -32,6 +36,10 @@ support-files =
[test_bug800817.xul]
[test_bug830396.xul]
[test_bug830858.xul]
[test_bug1224790-1.xul]
skip-if = os != 'cocoa'
[test_bug1224790-2.xul]
skip-if = os != 'cocoa'
[test_callback_wrapping.xul]
[test_clonewrapper.xul]
[test_cyclecollector.xul]

View File

@ -0,0 +1,33 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1224790
-->
<dialog title="Mozilla Bug 1224790"
buttons="accept"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
</body>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
/** Test for Bug 1224790 **/
function runTests() {
window.openDialog('file_bug1224790-1_nonmodal.xul', '', 'dialog=no');
}
function nonModalClosed() {
window.close();
opener.wrappedJSObject.modalClosed();
}
SimpleTest.waitForFocus(runTests);
]]>
</script>
</dialog>

View File

@ -0,0 +1,28 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1224790
-->
<window title="Mozilla Bug 1224790"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
</body>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
/** Test for Bug 1224790 **/
function runTests() {
window.close();
opener.wrappedJSObject.nonModalClosed();
}
SimpleTest.waitForFocus(runTests);
]]>
</script>
</window>

View File

@ -0,0 +1,35 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1224790
-->
<dialog title="Mozilla Bug 1224790"
buttons="accept"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
</body>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
/** Test for Bug 1224790 **/
var nonModal = null;
function runTests() {
nonModal = window.openDialog('file_bug1224790-2_nonmodal.xul', '', 'dialog=no');
}
function nonModalOpened() {
window.close();
nonModal.wrappedJSObject.modalClosed(opener);
}
SimpleTest.waitForFocus(runTests);
]]>
</script>
</dialog>

View File

@ -0,0 +1,46 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1224790
-->
<window title="Mozilla Bug 1224790"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
</body>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
/** Test for Bug 1224790 **/
function runTests() {
opener.wrappedJSObject.nonModalOpened()
}
function modalClosed(grandparent) {
// Request cycle collection to trigger destructor for parent modal window,
// that mutates mAncestorLink of this window.
const Ci = Components.interfaces;
const Cu = Components.utils;
var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
var windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDOMWindowUtils);
Services.obs.notifyObservers(null, "child-cc-request", null);
windowUtils.cycleCollect();
// Wait for a while.
setTimeout(function() {
window.close();
grandparent.wrappedJSObject.nonModalClosed();
}, 3000);
}
SimpleTest.waitForFocus(runTests);
]]>
</script>
</window>

View File

@ -0,0 +1,58 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1224790
-->
<window title="Mozilla Bug 1224790"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
/** Test for Bug 1224790 **/
/*
* 1. Opens modal dialog
* 2. Open non-modal window from modal dialog
* 3. Close non-modal window
* 4. Close modal dialog
* 5. Click button to ensure mouse event is working
*/
function startTest() {
window.openDialog('file_bug1224790-1_modal.xul', '', 'modal');
}
function modalClosed() {
SimpleTest.waitForFocus(gotFocus);
}
function gotFocus() {
var button = document.getElementById('button');
synthesizeMouseAtCenter(button, { type: 'mousemove' }, window);
// The bug is not reproducible with synthesizeMouseAtCenter.
// Need to emulate native mouse event.
synthesizeNativeOSXClick(button.boxObject.screenX + button.boxObject.width / 2,
button.boxObject.screenY + button.boxObject.height / 2);
}
function onClick() {
ok(true, "Click event should be fired");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(startTest);
]]>
</script>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1224790"
target="_blank">Mozilla Bug 1224790</a>
</body>
<button id="button" label="button" oncommand="onClick()" />
</window>

View File

@ -0,0 +1,59 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1224790
-->
<window title="Mozilla Bug 1224790"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
/** Test for Bug 1224790 **/
/*
* 1. Opens modal dialog
* 2. Open non-modal window from modal dialog
* 3. Close modal dialog
* 4. Wait for a while for destructor for modal dialog
* 5. Close non-modal window
* 6. Click button to ensure mouse event is working
*/
function startTest() {
window.openDialog('file_bug1224790-2_modal.xul', '', 'modal');
}
function nonModalClosed() {
SimpleTest.waitForFocus(gotFocus);
}
function gotFocus() {
var button = document.getElementById('button');
synthesizeMouseAtCenter(button, { type: 'mousemove' }, window);
// The bug is not reproducible with synthesizeMouseAtCenter.
// Need to emulate native mouse event.
synthesizeNativeOSXClick(button.boxObject.screenX + button.boxObject.width / 2,
button.boxObject.screenY + button.boxObject.height / 2);
}
function onClick() {
ok(true, "Click event should be fired");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(startTest);
]]>
</script>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1224790"
target="_blank">Mozilla Bug 1224790</a>
</body>
<button id="button" label="button" oncommand="onClick()" />
</window>

View File

@ -1053,7 +1053,7 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow* aParent,
nsCOMPtr<nsIWidget> parentWidget;
parentWindow->GetMainWidget(getter_AddRefs(parentWidget));
if (parentWidget) {
parentWidget->SetModal(true);
parentWidget->SetFakeModal(true);
}
}
} else {

View File

@ -15,6 +15,7 @@
* synthesizeNativeKey
* synthesizeMouseExpectEvent
* synthesizeKeyExpectEvent
* synthesizeNativeClick
*
* When adding methods to this file, please add a performance test for it.
*/
@ -36,6 +37,11 @@ window.__defineGetter__('_EU_Cc', function() {
return c.value && !c.writable ? Components.classes : SpecialPowers.Cc;
});
window.__defineGetter__('_EU_Cu', function() {
var c = Object.getOwnPropertyDescriptor(window, 'Components');
return c.value && !c.writable ? Components.utils : SpecialPowers.Cu;
});
/**
* Send a mouse event to the node aTarget (aTarget can be an id, or an
* actual node) . The "event" passed in to aEvent is just a JavaScript
@ -1501,3 +1507,101 @@ function synthesizeSelectionSet(aOffset, aLength, aReverse, aWindow)
var flags = aReverse ? SELECTION_SET_FLAG_REVERSE : 0;
return utils.sendSelectionSetEvent(aOffset, aLength, flags);
}
/*
* Synthesize a native mouse click event at a particular point in screen.
* This function should be used only for testing native event loop.
* Use synthesizeMouse instead for most case.
*
* This works only on OS X. Throws an error on other OS. Also throws an error
* when the library or any of function are not found, or something goes wrong
* in native functions.
*/
function synthesizeNativeOSXClick(x, y)
{
var { ctypes } = _EU_Cu.import("resource://gre/modules/ctypes.jsm", {});
// Library
var CoreFoundation = ctypes.open("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation");
var CoreGraphics = ctypes.open("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics");
// Contants
var kCGEventLeftMouseDown = 1;
var kCGEventLeftMouseUp = 2;
var kCGEventSourceStateHIDSystemState = 1;
var kCGHIDEventTap = 0;
var kCGMouseButtonLeft = 0;
var kCGMouseEventClickState = 1;
// Types
var CGEventField = ctypes.uint32_t;
var CGEventRef = ctypes.voidptr_t;
var CGEventSourceRef = ctypes.voidptr_t;
var CGEventSourceStateID = ctypes.uint32_t;
var CGEventTapLocation = ctypes.uint32_t;
var CGEventType = ctypes.uint32_t;
var CGFloat = ctypes.voidptr_t.size == 4 ? ctypes.float : ctypes.double;
var CGMouseButton = ctypes.uint32_t;
var CGPoint = new ctypes.StructType(
"CGPoint",
[ { "x" : CGFloat },
{ "y" : CGFloat } ]);
// Functions
var CGEventSourceCreate = CoreGraphics.declare(
"CGEventSourceCreate",
ctypes.default_abi,
CGEventSourceRef, CGEventSourceStateID);
var CGEventCreateMouseEvent = CoreGraphics.declare(
"CGEventCreateMouseEvent",
ctypes.default_abi,
CGEventRef,
CGEventSourceRef, CGEventType, CGPoint, CGMouseButton);
var CGEventSetIntegerValueField = CoreGraphics.declare(
"CGEventSetIntegerValueField",
ctypes.default_abi,
ctypes.void_t,
CGEventRef, CGEventField, ctypes.int64_t);
var CGEventPost = CoreGraphics.declare(
"CGEventPost",
ctypes.default_abi,
ctypes.void_t,
CGEventTapLocation, CGEventRef);
var CFRelease = CoreFoundation.declare(
"CFRelease",
ctypes.default_abi,
ctypes.void_t,
CGEventRef);
var source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
if (!source) {
throw new Error("CGEventSourceCreate returns null");
}
var loc = new CGPoint({ x: x, y: y });
var event = CGEventCreateMouseEvent(source, kCGEventLeftMouseDown, loc,
kCGMouseButtonLeft);
if (!event) {
throw new Error("CGEventCreateMouseEvent returns null");
}
CGEventSetIntegerValueField(event, kCGMouseEventClickState,
new ctypes.Int64(1));
CGEventPost(kCGHIDEventTap, event);
CFRelease(event);
event = CGEventCreateMouseEvent(source, kCGEventLeftMouseUp, loc,
kCGMouseButtonLeft);
if (!event) {
throw new Error("CGEventCreateMouseEvent returns null");
}
CGEventSetIntegerValueField(event, kCGMouseEventClickState,
new ctypes.Int64(1));
CGEventPost(kCGHIDEventTap, event);
CFRelease(event);
CFRelease(source);
CoreFoundation.close();
CoreGraphics.close();
}

View File

@ -268,6 +268,7 @@ public:
NS_IMETHOD Enable(bool aState) override;
virtual bool IsEnabled() const override;
NS_IMETHOD SetModal(bool aState) override;
NS_IMETHOD SetFakeModal(bool aState) override;
virtual bool IsVisible() const override;
NS_IMETHOD SetFocus(bool aState=false) override;
virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
@ -417,6 +418,7 @@ protected:
const IMENotification& aIMENotification) override;
nsIWidget* mParent; // if we're a popup, this is our parent [WEAK]
nsIWidget* mAncestorLink; // link to traverse ancestors [WEAK]
BaseWindow* mWindow; // our cocoa window [STRONG]
WindowDelegate* mDelegate; // our delegate for processing window msgs [STRONG]
RefPtr<nsMenuBarX> mMenuBar;
@ -438,6 +440,7 @@ protected:
bool mInFullScreenTransition; // true from the request to enter/exit fullscreen
// (MakeFullScreen() call) to EnteredFullScreen()
bool mModal;
bool mFakeModal;
// Only true on 10.7+ if SetShowsFullScreenButton(true) is called.
bool mSupportsNativeFullScreen;

View File

@ -99,6 +99,7 @@ static void RollUpPopups()
nsCocoaWindow::nsCocoaWindow()
: mParent(nullptr)
, mAncestorLink(nullptr)
, mWindow(nil)
, mDelegate(nil)
, mSheetWindowParent(nil)
@ -112,6 +113,7 @@ nsCocoaWindow::nsCocoaWindow()
, mInFullScreenMode(false)
, mInFullScreenTransition(false)
, mModal(false)
, mFakeModal(false)
, mSupportsNativeFullScreen(false)
, mInNativeFullScreenMode(false)
, mIsAnimationSuppressed(false)
@ -157,6 +159,7 @@ nsCocoaWindow::~nsCocoaWindow()
} else {
nsCocoaWindow* childWindow = static_cast<nsCocoaWindow*>(kid);
childWindow->mParent = nullptr;
childWindow->mAncestorLink = mAncestorLink;
kid = kid->GetPrevSibling();
}
}
@ -271,6 +274,7 @@ nsresult nsCocoaWindow::Create(nsIWidget *aParent,
Inherited::BaseCreate(aParent, newBounds, aInitData);
mParent = aParent;
mAncestorLink = aParent;
// Applications that use native popups don't want us to create popup windows.
if ((mWindowType == eWindowType_popup) && UseNativePopupWindows())
@ -505,6 +509,13 @@ NS_IMETHODIMP nsCocoaWindow::Destroy()
return NS_OK;
mOnDestroyCalled = true;
// SetFakeModal(true) is called for non-modal window opened by modal window.
// On Cocoa, it needs corresponding SetFakeModal(false) on destroy to restore
// ancestor windows' state.
if (mFakeModal) {
SetFakeModal(false);
}
// If we don't hide here we run into problems with panels, this is not ideal.
// (Bug 891424)
Show(false);
@ -604,7 +615,7 @@ NS_IMETHODIMP nsCocoaWindow::SetModal(bool aState)
nsAutoreleasePool localPool;
mModal = aState;
nsCocoaWindow *aParent = static_cast<nsCocoaWindow*>(mParent);
nsCocoaWindow *ancestor = static_cast<nsCocoaWindow*>(mAncestorLink);
if (aState) {
++gXULModalLevel;
// When a non-sheet window gets "set modal", make the window(s) that it
@ -615,16 +626,16 @@ NS_IMETHODIMP nsCocoaWindow::SetModal(bool aState)
// ShowModal() (each of these event loops is "exclusive", and can't run at
// the same time as other (similar) event loops).
if (mWindowType != eWindowType_sheet) {
while (aParent) {
if (aParent->mNumModalDescendents++ == 0) {
NSWindow *aWindow = aParent->GetCocoaWindow();
if (aParent->mWindowType != eWindowType_invisible) {
while (ancestor) {
if (ancestor->mNumModalDescendents++ == 0) {
NSWindow *aWindow = ancestor->GetCocoaWindow();
if (ancestor->mWindowType != eWindowType_invisible) {
[[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:NO];
[[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:NO];
[[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:NO];
}
}
aParent = static_cast<nsCocoaWindow*>(aParent->mParent);
ancestor = static_cast<nsCocoaWindow*>(ancestor->mParent);
}
[mWindow setLevel:NSModalPanelWindowLevel];
nsCocoaWindowList *windowList = new nsCocoaWindowList;
@ -639,17 +650,17 @@ NS_IMETHODIMP nsCocoaWindow::SetModal(bool aState)
--gXULModalLevel;
NS_ASSERTION(gXULModalLevel >= 0, "Mismatched call to nsCocoaWindow::SetModal(false)!");
if (mWindowType != eWindowType_sheet) {
while (aParent) {
if (--aParent->mNumModalDescendents == 0) {
NSWindow *aWindow = aParent->GetCocoaWindow();
if (aParent->mWindowType != eWindowType_invisible) {
while (ancestor) {
if (--ancestor->mNumModalDescendents == 0) {
NSWindow *aWindow = ancestor->GetCocoaWindow();
if (ancestor->mWindowType != eWindowType_invisible) {
[[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:YES];
[[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:YES];
[[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:YES];
}
}
NS_ASSERTION(aParent->mNumModalDescendents >= 0, "Widget hierarchy changed while modal!");
aParent = static_cast<nsCocoaWindow*>(aParent->mParent);
NS_ASSERTION(ancestor->mNumModalDescendents >= 0, "Widget hierarchy changed while modal!");
ancestor = static_cast<nsCocoaWindow*>(ancestor->mParent);
}
if (gGeckoAppModalWindowList) {
NS_ASSERTION(gGeckoAppModalWindowList->window == this, "Widget hierarchy changed while modal!");
@ -666,6 +677,12 @@ NS_IMETHODIMP nsCocoaWindow::SetModal(bool aState)
return NS_OK;
}
NS_IMETHODIMP nsCocoaWindow::SetFakeModal(bool aState)
{
mFakeModal = aState;
return SetModal(aState);
}
// Hide or show this window
NS_IMETHODIMP nsCocoaWindow::Show(bool bState)
{

View File

@ -123,8 +123,8 @@ typedef void* nsNativeWidget;
#endif
#define NS_IWIDGET_IID \
{ 0x7b736a0c, 0x2262, 0x4f37, \
{ 0xbd, 0xed, 0xe5, 0x60, 0x88, 0x1c, 0x36, 0xdd } }
{ 0xd953b7a1, 0x6981, 0x4ed7, \
{ 0xbc, 0xf0, 0xed, 0x96, 0x70, 0xee, 0x23, 0x28 } }
/*
* Window shadow styles
@ -594,6 +594,15 @@ class nsIWidget : public nsISupports {
*/
NS_IMETHOD SetModal(bool aModal) = 0;
/**
* Make the non-modal window opened by modal window fake-modal, that will
* call SetFakeModal(false) on destroy on Cocoa.
*/
NS_IMETHOD SetFakeModal(bool aModal)
{
return SetModal(aModal);
}
/**
* The maximum number of simultaneous touch contacts supported by the device.
* In the case of devices with multiple digitizers (e.g. multiple touch screens),