Bug 442228, support resizers in scrollable areas and enable them by default for textareas, r=roc,sr=dbaron

This commit is contained in:
Neil Deakin 2010-03-19 07:49:34 -04:00
parent 4bad2a09ab
commit 17876e41fa
15 changed files with 256 additions and 58 deletions

View File

@ -57,6 +57,7 @@ _TEST_FILES = test_bug231389.html \
test_bug476308.html \
test_bug477531.html \
test_bug477700.html \
test_textarea_resize.html \
test_bug478219.xhtml \
test_bug542914.html \
bug477700_subframe.html \

View File

@ -0,0 +1,77 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Bug 477700</title>
<script type="application/javascript" src="/MochiKit/packed.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<div id="content" style="display: none">
</div>
<textarea id="textarea" style="-moz-appearance: none; border: 0;">Text</textarea>
<pre id="test">
<script type="application/javascript">
/** Test for textbox resizing **/
SimpleTest.waitForExplicitFinish();
addLoadEvent(function() SimpleTest.executeSoon(doTheTest));
// First, test the default value which is 'both', then test explicitly
// setting each possible value.
var currentResize = "both";
var resizeTypes = [ "horizontal", "vertical", "none", "inherit", "both" ];
function doTheTest() {
var textarea = $("textarea");
var rect = textarea.getBoundingClientRect();
// assume that the resizer is in the lower right corner
synthesizeMouse(textarea, rect.width - 8, rect.height - 8, { type:"mousedown" });
synthesizeMouse(textarea, rect.width + 40, rect.height + 40, { type:"mousemove" });
var newrect = textarea.getBoundingClientRect();
var hchange = (currentResize == "both" || currentResize == "horizontal");
var vchange = (currentResize == "both" || currentResize == "vertical");
is(Math.round(newrect.width), Math.round(rect.width + (hchange ? 48 : 0)),
currentResize + " width has increased");
is(Math.round(newrect.height), Math.round(rect.height + (vchange ? 48 : 0)),
currentResize + " height has increased");
synthesizeMouse(textarea, rect.width - 20, rect.height - 20, { type:"mousemove" });
newrect = textarea.getBoundingClientRect();
is(Math.round(newrect.width), Math.round(rect.width - (hchange ? 12 : 0)),
currentResize + " width has decreased");
is(Math.round(newrect.height), Math.round(rect.height - (vchange ? 12 : 0)),
currentResize + " height has decreased");
synthesizeMouse(textarea, rect.width - 220, rect.height - 220, { type:"mousemove" });
newrect = textarea.getBoundingClientRect();
ok(hchange ? newrect.width >= 15 : Math.round(newrect.width) == Math.round(rect.width),
currentResize + " width decreased below minimum");
ok(vchange ? newrect.height >= 15 : Math.round(newrect.height) == Math.round(rect.height),
currentResize + " height decreased below minimum");
synthesizeMouse(textarea, rect.width - 8, rect.height - 8, { type:"mouseup" });
currentResize = resizeTypes.shift();
if (currentResize) {
textarea.style.width = "auto";
textarea.style.height = "auto";
textarea.style.MozResize = currentResize;
SimpleTest.executeSoon(doTheTest);
}
else {
SimpleTest.finish();
}
}
</script>
</pre>
</body>
</html>

View File

@ -1815,7 +1815,7 @@ nsGfxScrollFrameInner::BuildDisplayList(nsDisplayListBuilder* aBuilder,
{
nsresult rv = mOuter->DisplayBorderBackgroundOutline(aBuilder, aLists);
NS_ENSURE_SUCCESS(rv, rv);
if (aBuilder->GetIgnoreScrollFrame() == mOuter) {
// Don't clip the scrolled child, and don't paint scrollbars/scrollcorner.
// The scrolled frame shouldn't have its own background/border, so we
@ -1828,13 +1828,16 @@ nsGfxScrollFrameInner::BuildDisplayList(nsDisplayListBuilder* aBuilder,
// in the border-background layer, on top of our own background and
// borders and underneath borders and backgrounds of later elements
// in the tree.
nsIFrame* kid = mOuter->GetFirstChild(nsnull);
while (kid) {
PRBool hasResizer = HasResizer();
for (nsIFrame* kid = mOuter->GetFirstChild(nsnull); kid; kid = kid->GetNextSibling()) {
if (kid != mScrolledFrame) {
if (kid == mScrollCornerBox && hasResizer) {
// skip the resizer as this will be drawn later on top of the scrolled content
continue;
}
rv = mOuter->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists);
NS_ENSURE_SUCCESS(rv, rv);
}
kid = kid->GetNextSibling();
}
// Overflow clipping can never clip frames outside our subtree, so there
@ -1860,6 +1863,15 @@ nsGfxScrollFrameInner::BuildDisplayList(nsDisplayListBuilder* aBuilder,
rv = mOuter->OverflowClip(aBuilder, set, aLists, clip, PR_TRUE, mIsRoot);
NS_ENSURE_SUCCESS(rv, rv);
// Place the resizer in the display list above the overflow clip. This
// ensures that the resizer appears above the content and the mouse can
// still target the resizer even when scrollbars are hidden.
if (hasResizer && mScrollCornerBox) {
rv = mOuter->BuildDisplayListForChild(aBuilder, mScrollCornerBox, aDirtyRect, aLists,
nsIFrame::DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
@ -2176,6 +2188,19 @@ nsGfxScrollFrameInner::CreateAnonymousContent(nsTArray<nsIContent*>& aElements)
}
}
// Check if the frame is resizable.
nsIFrame* resizableFrame = mOuter;
if (parent) {
// For textarea, mOuter is the frame for the anonymous div element,
// so get the resizability from the parent textarea instead.
nsCOMPtr<nsIDOMHTMLTextAreaElement> textAreaElement(do_QueryInterface(parent->GetContent()));
if (textAreaElement) {
resizableFrame = parent;
}
}
PRBool isResizable = resizableFrame->GetStyleDisplay()->mResize != NS_STYLE_RESIZE_NONE;
nsIScrollableFrame *scrollable = do_QueryFrame(mOuter);
// At this stage in frame construction, the document element and/or
@ -2195,7 +2220,7 @@ nsGfxScrollFrameInner::CreateAnonymousContent(nsTArray<nsIContent*>& aElements)
ScrollbarStyles styles = scrollable->GetScrollbarStyles();
PRBool canHaveHorizontal = styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN;
PRBool canHaveVertical = styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN;
if (!canHaveHorizontal && !canHaveVertical) {
if (!canHaveHorizontal && !canHaveVertical && !isResizable) {
// Nothing to do.
return NS_OK;
}
@ -2240,7 +2265,42 @@ nsGfxScrollFrameInner::CreateAnonymousContent(nsTArray<nsIContent*>& aElements)
return NS_ERROR_OUT_OF_MEMORY;
}
if (canHaveHorizontal && canHaveVertical) {
if (isResizable) {
nsCOMPtr<nsINodeInfo> nodeInfo;
nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::resizer, nsnull,
kNameSpaceID_XUL);
NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
rv = NS_NewXULElement(getter_AddRefs(mScrollCornerContent), nodeInfo);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString dir;
switch (resizableFrame->GetStyleDisplay()->mResize) {
case NS_STYLE_RESIZE_HORIZONTAL:
if (IsScrollbarOnRight()) {
dir.AssignLiteral("right");
}
else {
dir.AssignLiteral("left");
}
break;
case NS_STYLE_RESIZE_VERTICAL:
dir.AssignLiteral("bottom");
break;
case NS_STYLE_RESIZE_BOTH:
dir.AssignLiteral("bottomend");
break;
default:
NS_WARNING("only resizable types should have resizers");
}
mScrollCornerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, dir, PR_FALSE);
mScrollCornerContent->SetAttr(kNameSpaceID_None, nsGkAtoms::element,
NS_LITERAL_STRING("_parent"), PR_FALSE);
if (!aElements.AppendElement(mScrollCornerContent))
return NS_ERROR_OUT_OF_MEMORY;
}
else if (canHaveHorizontal && canHaveVertical) {
nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollcorner, nsnull,
kNameSpaceID_XUL);
rv = NS_NewElement(getter_AddRefs(mScrollCornerContent),
@ -2972,26 +3032,36 @@ static void LayoutAndInvalidate(nsBoxLayoutState& aState,
}
}
static void AdjustScrollbarRect(nsIFrame* aFrame, nsPresContext* aPresContext,
nsRect& aRect, PRBool aVertical)
void
nsGfxScrollFrameInner::AdjustScrollbarRectForResizer(
nsIFrame* aFrame, nsPresContext* aPresContext,
nsRect& aRect, PRBool aHasResizer, PRBool aVertical)
{
if ((aVertical ? aRect.width : aRect.height) == 0)
return;
nsPoint offsetToView;
nsPoint offsetToWidget;
nsIWidget* widget =
aFrame->GetClosestView(&offsetToView)->GetNearestWidget(&offsetToWidget);
nsPoint offset = offsetToView + offsetToWidget;
nsIntRect widgetRect;
if (!widget || !widget->ShowsResizeIndicator(&widgetRect))
return;
// if a content resizer is present, use its size. Otherwise, check if the
// widget has a resizer.
nsRect resizerRect;
if (aHasResizer && mScrollCornerBox) {
resizerRect = mScrollCornerBox->GetRect();
}
else {
nsPoint offsetToView;
nsPoint offsetToWidget;
nsIWidget* widget =
aFrame->GetClosestView(&offsetToView)->GetNearestWidget(&offsetToWidget);
nsPoint offset = offsetToView + offsetToWidget;
nsIntRect widgetRect;
if (!widget || !widget->ShowsResizeIndicator(&widgetRect))
return;
nsRect resizerRect =
nsRect(aPresContext->DevPixelsToAppUnits(widgetRect.x) - offset.x,
aPresContext->DevPixelsToAppUnits(widgetRect.y) - offset.y,
aPresContext->DevPixelsToAppUnits(widgetRect.width),
aPresContext->DevPixelsToAppUnits(widgetRect.height));
nsRect resizerRect =
nsRect(aPresContext->DevPixelsToAppUnits(widgetRect.x) - offset.x,
aPresContext->DevPixelsToAppUnits(widgetRect.y) - offset.y,
aPresContext->DevPixelsToAppUnits(widgetRect.width),
aPresContext->DevPixelsToAppUnits(widgetRect.height));
}
if (!resizerRect.Contains(aRect.BottomRight() - nsPoint(1, 1)))
return;
@ -3010,18 +3080,62 @@ nsGfxScrollFrameInner::LayoutScrollbars(nsBoxLayoutState& aState,
NS_ASSERTION(!mSupppressScrollbarUpdate,
"This should have been suppressed");
PRBool hasResizer = HasResizer();
PRBool scrollbarOnLeft = !IsScrollbarOnRight();
// place the scrollcorner
if (mScrollCornerBox) {
NS_PRECONDITION(mScrollCornerBox->IsBoxFrame(), "Must be a box frame!");
// if a resizer is present, get its size
nsSize resizerSize;
if (HasResizer()) {
// just assume a default size of 15 pixels
nscoord defaultSize = nsPresContext::CSSPixelsToAppUnits(15);
resizerSize.width =
mVScrollbarBox ? mVScrollbarBox->GetMinSize(aState).width : defaultSize;
resizerSize.height =
mHScrollbarBox ? mHScrollbarBox->GetMinSize(aState).height : defaultSize;
}
else {
resizerSize = nsSize(0, 0);
}
nsRect r(0, 0, 0, 0);
if (aContentArea.x != mScrollPort.x || scrollbarOnLeft) {
// scrollbar (if any) on left
r.x = aContentArea.x;
r.width = PR_MAX(resizerSize.width, mScrollPort.x - aContentArea.x);
NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
} else {
// scrollbar (if any) on right
r.width = PR_MAX(resizerSize.width, aContentArea.XMost() - mScrollPort.XMost());
r.x = aContentArea.XMost() - r.width;
NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
}
if (aContentArea.y != mScrollPort.y) {
NS_ERROR("top scrollbars not supported");
} else {
// scrollbar (if any) on bottom
r.height = PR_MAX(resizerSize.height, aContentArea.YMost() - mScrollPort.YMost());
r.y = aContentArea.YMost() - r.height;
NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect");
}
LayoutAndInvalidate(aState, mScrollCornerBox, r);
}
nsPresContext* presContext = mScrolledFrame->PresContext();
if (mVScrollbarBox) {
NS_PRECONDITION(mVScrollbarBox->IsBoxFrame(), "Must be a box frame!");
nsRect vRect(mScrollPort);
vRect.width = aContentArea.width - mScrollPort.width;
vRect.x = IsScrollbarOnRight() ? mScrollPort.XMost() : aContentArea.x;
vRect.x = scrollbarOnLeft ? aContentArea.x : mScrollPort.XMost();
#ifdef DEBUG
nsMargin margin;
mVScrollbarBox->GetMargin(margin);
NS_ASSERTION(margin == nsMargin(0,0,0,0), "Scrollbar margin not supported");
#endif
AdjustScrollbarRect(mOuter, presContext, vRect, PR_TRUE);
AdjustScrollbarRectForResizer(mOuter, presContext, vRect, hasResizer, PR_TRUE);
LayoutAndInvalidate(aState, mVScrollbarBox, vRect);
}
@ -3035,39 +3149,10 @@ nsGfxScrollFrameInner::LayoutScrollbars(nsBoxLayoutState& aState,
mHScrollbarBox->GetMargin(margin);
NS_ASSERTION(margin == nsMargin(0,0,0,0), "Scrollbar margin not supported");
#endif
AdjustScrollbarRect(mOuter, presContext, hRect, PR_FALSE);
AdjustScrollbarRectForResizer(mOuter, presContext, hRect, hasResizer, PR_FALSE);
LayoutAndInvalidate(aState, mHScrollbarBox, hRect);
}
// place the scrollcorner
if (mScrollCornerBox) {
NS_PRECONDITION(mScrollCornerBox->IsBoxFrame(), "Must be a box frame!");
nsRect r(0, 0, 0, 0);
if (aContentArea.x != mScrollPort.x) {
// scrollbar (if any) on left
r.x = aContentArea.x;
r.width = mScrollPort.x - aContentArea.x;
NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
} else {
// scrollbar (if any) on right
r.x = mScrollPort.XMost();
r.width = aContentArea.XMost() - mScrollPort.XMost();
NS_ASSERTION(r.width >= 0, "Scroll area should be inside client rect");
}
if (aContentArea.y != mScrollPort.y) {
// scrollbar (if any) on top
r.y = aContentArea.y;
r.height = mScrollPort.y - aContentArea.y;
NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect");
} else {
// scrollbar (if any) on bottom
r.y = mScrollPort.YMost();
r.height = aContentArea.YMost() - mScrollPort.YMost();
NS_ASSERTION(r.height >= 0, "Scroll area should be inside client rect");
}
LayoutAndInvalidate(aState, mScrollCornerBox, r);
}
// may need to update fixed position children of the viewport,
// if the client area changed size because of an incremental
// reflow of a descendant. (If the outer frame is dirty, the fixed

View File

@ -215,6 +215,15 @@ public:
nsMargin GetDesiredScrollbarSizes(nsBoxLayoutState* aState);
PRBool IsLTR() const;
PRBool IsScrollbarOnRight() const;
// adjust the scrollbar rectangle aRect to account for any visible resizer.
// aHasResizer specifies if there is a content resizer, however this method
// will also check if a widget resizer is present as well.
void AdjustScrollbarRectForResizer(nsIFrame* aFrame, nsPresContext* aPresContext,
nsRect& aRect, PRBool aHasResizer, PRBool aVertical);
// returns true if a resizer should be visible
PRBool HasResizer() {
return mScrollCornerContent && mScrollCornerContent->Tag() == nsGkAtoms::resizer;
}
void LayoutScrollbars(nsBoxLayoutState& aState,
const nsRect& aContentArea,
const nsRect& aOldScrollArea);

View File

@ -1,6 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<textarea style="display: block; border: none; width: 200px; height: 200px; padding: 50px; background: green"></textarea>
<textarea style="display: block; border: none; width: 200px; height: 200px; padding: 50px; background: green; -moz-resize: none;"></textarea>
</body>
</html>

View File

@ -1,6 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<textarea style="display: block; border: none; width: 200px; height: 200px; padding: 50px; background: green; max-width: 200px;"></textarea>
<textarea style="display: block; border: none; width: 200px; height: 200px; padding: 50px; background: green; max-width: 200px; -moz-resize: none;"></textarea>
</body>
</html>

View File

@ -1,6 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<textarea style="display: block; border: none; width: 200px; height: 200px; padding: 50px; background: green; max-height: 200px;"></textarea>
<textarea style="display: block; border: none; width: 200px; height: 200px; padding: 50px; background: green; max-height: 200px; -moz-resize: none;"></textarea>
</body>
</html>

View File

@ -8,6 +8,6 @@
<script type="text/javascript" src="platform.js"/>
<html:textarea rows="10"/>
<html:textarea rows="10" style="-moz-resize: none;"/>
</window>

View File

@ -3,6 +3,6 @@
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<html:textarea xmlns:html="http://www.w3.org/1999/xhtml" style="width: 200px; height: 200px; margin: 0;"/>
<html:textarea xmlns:html="http://www.w3.org/1999/xhtml" style="width: 200px; height: 200px; margin: 0; -moz-resize: none;"/>
</window>

View File

@ -127,6 +127,7 @@ textarea {
letter-spacing: normal;
vertical-align: text-bottom;
cursor: text;
-moz-resize: both;
-moz-binding: url("chrome://global/content/platformHTMLBindings.xml#textAreas");
-moz-appearance: textfield-multiline;
text-indent: 0;

View File

@ -0,0 +1,2 @@
== textbox-multiline-noresize.xul textbox-multiline-ref.xul
!= textbox-multiline-resize.xul textbox-multiline-ref.xul

View File

@ -0,0 +1,5 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window align="start" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<textbox style="margin: 0;" multiline="true" width="100" height="100"/>
</window>

View File

@ -0,0 +1,5 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window align="start" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<vbox style="-moz-appearance: textfield;" width="100" height="100"/>
</window>

View File

@ -0,0 +1,5 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window align="start" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<textbox style="margin: 0;" resizable="true" multiline="true" width="100" height="100"/>
</window>

View File

@ -694,6 +694,14 @@ textbox[multiline="true"] {
-moz-binding: url("chrome://global/content/bindings/textbox.xml#input-box");
}
html|textarea.textbox-textarea {
-moz-resize: none;
}
textbox[resizable="true"] > .textbox-input-box > html|textarea.textbox-textarea {
-moz-resize: both;
}
.textbox-input-box[spellcheck="true"] {
-moz-binding: url("chrome://global/content/bindings/textbox.xml#input-box-spell");
}